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