xref: /expo/docs/pages/guides/using-nextjs.mdx (revision ae642c8a)
1---
2title: Using Next.js with Expo for Web
3sidebar_title: Using 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][nextjs] 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`][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, this doesn't provide Server-Side Rendering (SSR) for native apps.
17
18## Setup
19
20To get started, create a new project with [the template](https://github.com/expo/examples/tree/master/with-nextjs):
21
22<Terminal cmd={["$ npx create-expo-app -e with-nextjs"]} />
23
24- **Native**: `npx expo start` -- start the Expo project
25- **Web**: `npx next dev` -- 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
41  When using Next.js with SWC, 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
66  Adjust 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## Guides
228
229### Deploy to Vercel
230
231(Formerly ZEIT Now)
232
233This is Vercel's preferred method for deploying Next.js projects to production.
234
235- Add a **build** script to your **package.json**
236  ```json package.json
237  {
238    "scripts": {
239      "build": "next build"
240    }
241  }
242  ```
243- Install the Vercel CLI: `npm i -g vercel`
244- Deploy to Vercel: `vercel`
245
246## Limitations or differences comparing to the default Expo for Web
247
248Using 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.
249
250- Expo Next.js adapter does not support the experimental app directory.
251- For File-based routing on native, we recommend using [Expo Router](https://github.com/expo/router).
252
253## Contributing
254
255If you would like to help make Next.js support in Expo better, feel free to open a PR or submit an issue:
256
257- [@expo/next-adapter][next-adapter]
258
259## Troubleshooting
260
261### Cannot use import statement outside a module
262
263Figure out which module has the import statement and add it to the `transpilePackages` option in **next.config.js**:
264
265```js next.config.js
266const { withExpo } = require('@expo/next-adapter');
267
268module.exports = withExpo({
269  experimental: {
270    transpilePackages: [
271      'react-native',
272      'expo',
273      // Add failing package here, and restart the server...
274    ],
275  },
276});
277```
278
279{/* Footer */}
280
281[expo-packages]: https://github.com/expo/expo/tree/main/packages
282[nextjs]: https://nextjs.org/
283[next-adapter]: https://github.com/expo/expo-cli/tree/main/packages/next-adapter
284[next-docs]: https://nextjs.org/docs
285[next-optimized-images]: https://github.com/cyrilwanner/next-optimized-images
286