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` — 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 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