Next.js in React-Native Monorepo



After adding TV support to Matteo Mazzarolo's monorepo, the only platform missing (that I care about) is Next.js.

At anytime, if you get the lstat error when trying to install something, just rerun the command. It seems like a common issue that pops up in yarn 1 workspaces sometimes.

Initialize Next App

% cd packages
% yarn create next-app --typescript
✔ What is your project named? … next
% cd next
% yarn dev
% yarn add react-native-web @react-native-async-storage/async-storage
% yarn add -D babel-plugin-react-native-web babel-plugin-transform-define @types/react-native

Update package.json

Let's add a package name, version, and our app dependency.

	"name": "@my-app/next",
	"version": "0.0.1",
	"dependencies": {
		"@my-app/app": "*",
% yarn

Replace Next App with the Monorepo App

Let's replace the current Next app with our monorepo app and also polyfill setImmediate.

% yarn add setimmediate
// packages/next/pages/index.tsx
import 'setimmediate';
import { App } from '@my-app/app';

export default App;

Configure Next Document

We need to update _document.tsx to register our app and handle the root styling (e.g. expanding the root div to 100% height).

// packages/next/pages/_document.tsx
import * as React from 'react'
import { Children } from 'react'
import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'
import { AppRegistry } from 'react-native'

import { name as appName } from '../package.json'

// Force Next-generated DOM elements to fill their parent's height
const normalizeNextElements = `
  #__next {
    display: flex;
    flex-direction: column;
    height: 100%;
  html {
    height: 100%;
  body {
    height: 100%;
    overflow: hidden;

export default class MyDocument extends Document {
  static async getInitialProps({ renderPage }: DocumentContext) {
    AppRegistry.registerComponent(appName, () => Main)
    const { getStyleElement } = AppRegistry.getApplication(appName)
    const page = await renderPage()
    const styles = [
      <style key='normalizeNextElements' dangerouslySetInnerHTML={{ __html: normalizeNextElements }} />,
    return {, styles: Children.toArray(styles) }

  render() {
    return (
        <Head />
          <Main />
          <NextScript />

Update next.config.js

In order to work with the monorepo and react-native-web, we need to do a few things,

  • Enable the experimental externalDir feature (or we can use next-transpile-modules).
  • Alias react and react-native-web so that Next can resolve them.
  • Add plugins to handle static asset imports.
% yarn add next-images next-fonts
// packages/next/next.config.js
/** @type {import('next').NextConfig} */
const path = require("path");

// Necessary to handle statically imported images
const withImages = require('next-images');
// Necessary to handle statically imported fonts
const withFonts = require('next-fonts');

module.exports = withImages(withFonts({
  // Allows us to access other directories in the monorepo
  experimental: {
    externalDir: true
  // This feature conflicts with next-images
  images: {
    disableStaticImages: true,
  webpack: (config, options) => {
    if (options.isServer) {
      config.externals = ['react', 'react-native-web', ...config.externals];
    config.resolve.alias['react'] = path.resolve(__dirname, '.', 'node_modules', 'react');
    config.resolve.alias['react-native-web'] = path.resolve(__dirname, '.', 'node_modules', 'react-native-web');

    return config;

Configure Babel

We need to create a custom babel.config.js in order to handle react-native-web as well as to define __DEV__ and __SUBPLATFORM__.

// packages/next/babel.config.js
module.exports = {
  presets: ['next/babel'],
  plugins: [
    ['react-native-web', { commonjs: true }],
    ['transform-define', {
      '__DEV__': process.env.NODE_ENV,
      '__SUBPLATFORM__': 'next',

Configure tsconfig.json

We need to extend from the root tsconfig.json and keep the necessary settings for Next.

// packages/next/tsconfig.json
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    "jsx": "preserve",
    "module": "esnext"
  "include": [
  "exclude": [

Add Root Scripts

Now, we can add some scripts to our root package.json so we can use the Next app from the monorepo root.

// package.json
	"scripts": {
			"next:start": "yarn workspace @my-app/next dev",
	    "next:build": "yarn workspace @my-app/next build",
	    "next:serve": "yarn workspace @my-app/next start",

Closing Thoughts

Next has always been my goto for web projects so I'm excited to see how the DX is from react-native. The navigation in Next differs from the normal react-navigation flow that fits well with most of the other platforms, but Fernando Rojo's expo-next-react-navigation aims to bridge this gap.

Chris' bitmoji smiling

Hi, how's it going? I’m Chris Finn, a full-stack developer from Boston, MA. I specialize in UI and UX design, cross-platform app development, and eating calzone. I am also a sucker for automation and security (in a past life I was a cybersecurity researcher).