xref: /expo/docs/pages/guides/using-nextjs.mdx (revision 9c8c85f2)
1---
2title: Use Next.js with Expo for Web
3sidebar_title: Use Next.js
4description: A guide for integrating Next.js with Expo for the web.
5---
6
7import { Collapsible } from '~/ui/components/Collapsible';
8import { Terminal } from '~/ui/components/Snippet';
9
10> **warning:** Using Next.js is not an official part of Expo's universal app development workflow.
11
12[Next.js](https://nextjs.org/) is a React framework that provides simple page-based routing as well as server-side rendering. To use Next.js with the Expo SDK, we recommend using [`@expo/next-adapter`](https://github.com/expo/expo-cli/tree/main/packages/next-adapter) library to handle the configuration.
13
14Using Expo with Next.js means you can share some of your existing components and APIs across your mobile and web app. Next.js has its own CLI that you'll need to use when developing for the web platform, so **you'll need to start your web projects with the Next.js CLI and not with `npx expo start`**.
15
16> Next.js can only be used with Expo for web as there is no support for Server-Side Rendering (SSR) for native apps.
17
18## Automatic setup
19
20To quickly get started, create a new project using [with-nextjs](https://github.com/expo/examples/tree/master/with-nextjs) template:
21
22<Terminal cmd={['$ npx create-expo-app -e with-nextjs']} />
23
24- **Native**: `npx expo start` &mdash; start the Expo project
25- **Web**: `npx next dev` &mdash; start the Next.js project
26
27## Manual setup
28
29### Install dependencies
30
31Ensure you have `expo`, `next`, `@expo/next-adapter` installed in your project:
32
33<Terminal cmd={['$ yarn add expo next @expo/next-adapter']} />
34
35### Transpilation
36
37Configure Next.js to transform language features:
38
39<Collapsible summary={<>Next.js with swc. (Recommended)</>}>
40
41Using Next.js with SWC is recommended. You can configure the **babel.config.js** to only account for native:
42
43```js babel.config.js
44module.exports = function (api) {
45  api.cache(true);
46  return {
47    presets: ['babel-preset-expo'],
48  };
49};
50```
51
52You will also have to [force Next.js to use SWC](https://nextjs.org/docs/messages/swc-disabled) by adding the following to your **next.config.js**:
53
54```js next.config.js
55module.exports = {
56  experimental: {
57    forceSwcTransforms: true,
58  },
59};
60```
61
62</Collapsible>
63
64<Collapsible summary={<>Next.js with Babel. (Not recommended)</>}>
65
66Adjust your **babel.config.js** to conditionally add `next/babel` when bundling with webpack for web:
67
68```js babel.config.js
69module.exports = function (api) {
70  // Detect web usage (this may change in the future if Next.js changes the loader)
71  const isWeb = api.caller(
72    caller =>
73      caller && (caller.name === 'babel-loader' || caller.name === 'next-babel-turbo-loader')
74  );
75  return {
76    presets: [
77      // Only use next in the browser, it'll break your native project
78      isWeb && require('next/babel'),
79      'babel-preset-expo',
80    ].filter(Boolean),
81  };
82};
83```
84
85</Collapsible>
86
87### Next.js configuration
88
89Add the following to your **next.config.js**:
90
91```js next.config.js
92const { withExpo } = require('@expo/next-adapter');
93
94module.exports = withExpo({
95  // transpilePackages is a Next.js +13.1 feature.
96  // older versions can use next-transpile-modules
97  transpilePackages: [
98    'react-native',
99    'expo',
100    // Add more React Native/Expo packages here...
101  ],
102});
103```
104
105The fully qualified Next.js config may look like:
106
107```js next.config.js
108const { withExpo } = require('@expo/next-adapter');
109
110/** @type {import('next').NextConfig} */
111const nextConfig = withExpo({
112  reactStrictMode: true,
113  swcMinify: true,
114  transpilePackages: [
115    'react-native',
116    'expo',
117    // Add more React Native/Expo packages here...
118  ],
119  experimental: {
120    forceSwcTransforms: true,
121  },
122});
123
124module.exports = nextConfig;
125```
126
127### React Native Web styling
128
129The package `react-native-web` builds on the assumption of reset CSS styles. Here's how you reset styles in Next.js using the **pages** directory.
130
131```js pages/_document.js
132import { Children } from 'react';
133import Document, { Html, Head, Main, NextScript } from 'next/document';
134import { AppRegistry } from 'react-native';
135
136// Follows the setup for react-native-web:
137// https://necolas.github.io/react-native-web/docs/setup/#root-element
138// Plus additional React Native scroll and text parity styles for various
139// browsers.
140// Force Next-generated DOM elements to fill their parent's height
141const style = `
142html, body, #__next {
143  -webkit-overflow-scrolling: touch;
144}
145#__next {
146  display: flex;
147  flex-direction: column;
148  height: 100%;
149}
150html {
151  scroll-behavior: smooth;
152  -webkit-text-size-adjust: 100%;
153}
154body {
155  /* Allows you to scroll below the viewport; default value is visible */
156  overflow-y: auto;
157  overscroll-behavior-y: none;
158  text-rendering: optimizeLegibility;
159  -webkit-font-smoothing: antialiased;
160  -moz-osx-font-smoothing: grayscale;
161  -ms-overflow-style: scrollbar;
162}
163`;
164
165export default class MyDocument extends Document {
166  static async getInitialProps({ renderPage }) {
167    AppRegistry.registerComponent('main', () => Main);
168    const { getStyleElement } = AppRegistry.getApplication('main');
169    const page = await renderPage();
170    const styles = [
171      <style key="react-native-style" dangerouslySetInnerHTML={{ __html: style }} />,
172      getStyleElement(),
173    ];
174    return { ...page, styles: Children.toArray(styles) };
175  }
176
177  render() {
178    return (
179      <Html style={{ height: '100%' }}>
180        <Head />
181        <body style={{ height: '100%', overflow: 'hidden' }}>
182          <Main />
183          <NextScript />
184        </body>
185      </Html>
186    );
187  }
188}
189```
190
191```js pages/_app.js
192import Head from 'next/head';
193
194export default function App({ Component, pageProps }) {
195  return (
196    <>
197      <Head>
198        <meta name="viewport" content="width=device-width, initial-scale=1" />
199      </Head>
200      <Component {...pageProps} />
201    </>
202  );
203}
204```
205
206## Transpiling modules
207
208By default, modules in the React Native ecosystem are not transpiled to run in web browsers. React Native relies on advanced caching in Metro to reload quickly. Next.js uses webpack, which does not have the same level of caching, so no node modules are transpiled by default. You will have to manually mark every module you want to transpile with the `transpilePackages` option in **next.config.js**:
209
210```js next.config.js
211const { withExpo } = require('@expo/next-adapter');
212
213module.exports = withExpo({
214  experimental: {
215    transpilePackages: [
216      // NOTE: Even though `react-native` is never used in Next.js,
217      // you need to list `react-native` because `react-native-web`
218      // is aliased to `react-native`. Adding `react-native-web` will not work.
219      'react-native',
220      'expo',
221      // Add more React Native/Expo packages here...
222    ],
223  },
224});
225```
226
227## Deploy to Vercel
228
229This is Vercel's preferred method for deploying Next.js projects to production.
230
231- Add a **build** script to your **package.json**
232  ```json package.json
233  {
234    "scripts": {
235      "build": "next build"
236    }
237  }
238  ```
239- Install the Vercel CLI:
240
241  <Terminal cmd={['$ npm i -g vercel']} />
242
243- Deploy to Vercel:
244
245  <Terminal cmd={['$ vercel']} />
246
247## Limitations or differences compared to the default Expo for Web
248
249Using Next.js for the web means you will be bundling with the Next.js webpack config. This will lead to some core differences in how you develop your app vs your website.
250
251- Expo Next.js adapter does not support the experimental **app** directory.
252- For file-based routing on native, we recommend using [Expo Router](https://github.com/expo/router).
253
254## Contributing
255
256If you would like to help make Next.js support in Expo better, feel free to open a PR or submit an issue:
257
258- [@expo/next-adapter](https://github.com/expo/expo-cli/tree/main/packages/next-adapter)
259
260## Troubleshooting
261
262### Cannot use import statement outside a module
263
264Figure out which module has the import statement and add it to the `transpilePackages` option in **next.config.js**:
265
266```js next.config.js
267const { withExpo } = require('@expo/next-adapter');
268
269module.exports = withExpo({
270  experimental: {
271    transpilePackages: [
272      'react-native',
273      'expo',
274      // Add the failing package here, and restart the server...
275    ],
276  },
277});
278```
279