Next.js in React-Native Monorepo
🌐

Next.js in React-Native Monorepo

TL;DR

After adding TV support to Matteo Mazzarolo's monorepo, the only platform missing (that I care about) is Next.js.
notion image
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 }} />, getStyleElement(), ] return { ...page, styles: Children.toArray(styles) } } render() { return ( <Html> <Head /> <body> <Main /> <NextScript /> </body> </Html>) } }

Update next.config.js

In order to work with the monorepo and react-native-web, we need to do a few things,
  • 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": [ "next-env.d.ts", "public", "pages" ], "exclude": [ "node_modules" ] }

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.
Click to rocket boost to the top of the page!