将 Next.js 与 Expo for Web 结合使用

将 Next.js 与 Expo 网页版集成的指南。


警告 使用 Next.js 并不是 Expo 通用应用开发工作流程的官方部分。

Next.js 是一个 React 框架,提供简单的基于页面的路由以及服务端渲染。要在 Expo SDK 中使用 Next.js,我们推荐使用 @expo/next-adapter 库来处理配置。

在 Next.js 中使用 Expo 意味着你可以在移动端和网页应用之间共享一些现有的组件和 API。Next.js 有自己的 CLI,在为网页平台开发时你需要使用它,因此 你需要用 Next.js CLI 来启动你的网页项目,而不是使用 npx expo start

🌐 Using 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.

Next.js 只能与 Expo 的 Web 版本一起使用,因为原生应用不支持服务器端渲染 (SSR)。

自动设置

🌐 Automatic setup

要快速开始,请使用 with-nextjs 模板创建一个新项目:

🌐 To quickly get started, create a new project using with-nextjs template:

Terminal
npx create-expo-app -e with-nextjs
  • 本地npx expo start — 启动 Expo 项目
  • 网络npx next dev — 启动 Next.js 项目

手动设置

🌐 Manual setup

安装依赖

🌐 Install dependencies

确保你的项目中已安装 exponext@expo/next-adapter

🌐 Ensure you have expo, next, @expo/next-adapter installed in your project:

Terminal
yarn add expo next @expo/next-adapter

转译

🌐 Transpilation

配置 Next.js 来转换语言特性:

🌐 Configure Next.js to transform language features:

Next.js 使用 swc。(推荐)

推荐在 Next.js 中使用 SWC。你可以配置 babel.config.js 以仅考虑原生功能:

🌐 Using Next.js with SWC is recommended. You can configure the babel.config.js to only account for native:

babel.config.js
module.exports = function (api) { api.cache(true); return { presets: ['babel-preset-expo'], }; };

你还需要通过在 next.config.js 中添加以下内容来强制 Next.js 使用 SWC

🌐 You will also have to force Next.js to use SWC by adding the following to your next.config.js:

next.config.js
module.exports = { experimental: { forceSwcTransforms: true, }, };
Next.js 使用 Babel。(不推荐)

调整你的 babel.config.js,以便在使用 webpack 为 web 打包时有条件地添加 next/babel

🌐 Adjust your babel.config.js to conditionally add next/babel when bundling with webpack for web:

babel.config.js
module.exports = function (api) { // Detect web usage (this may change in the future if Next.js changes the loader) const isWeb = api.caller( caller => caller && (caller.name === 'babel-loader' || caller.name === 'next-babel-turbo-loader') ); return { presets: [ // Only use next in the browser, it'll break your native project isWeb && require('next/babel'), 'babel-preset-expo', ].filter(Boolean), }; };

Next.js 配置

🌐 Next.js configuration

将以下内容添加到你的 next.config.js 中:

🌐 Add the following to your next.config.js:

next.config.js
const { withExpo } = require('@expo/next-adapter'); module.exports = withExpo({ // transpilePackages is a Next.js +13.1 feature. // older versions can use next-transpile-modules transpilePackages: [ 'react-native', 'react-native-web', 'expo', // Add more React Native/Expo packages here... ], });

完整的 Next.js 配置可能如下所示:

🌐 The fully qualified Next.js config may look like:

next.config.js
const { withExpo } = require('@expo/next-adapter'); /** @type {import('next').NextConfig} */ const nextConfig = withExpo({ reactStrictMode: true, swcMinify: true, transpilePackages: [ 'react-native', 'react-native-web', 'expo', // Add more React Native/Expo packages here... ], experimental: { forceSwcTransforms: true, }, }); module.exports = nextConfig;

React Native Web 样式

🌐 React Native Web styling

react-native-web 的构建基于重置 CSS 样式的假设。以下是在 Next.js 中使用 pages 目录重置样式的方法。

🌐 The 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.

pages/_document.js
import { Children } from 'react'; import Document, { Html, Head, Main, NextScript } from 'next/document'; import { AppRegistry } from 'react-native'; // Follows the setup for react-native-web: // https://necolas.github.io/react-native-web/docs/setup/#root-element // Plus additional React Native scroll and text parity styles for various // browsers. // Force Next-generated DOM elements to fill their parent's height const style = ` html, body, #__next { -webkit-overflow-scrolling: touch; } #__next { display: flex; flex-direction: column; height: 100%; } html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; } body { /* Allows you to scroll below the viewport; default value is visible */ overflow-y: auto; overscroll-behavior-y: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -ms-overflow-style: scrollbar; } `; export default class MyDocument extends Document { static async getInitialProps({ renderPage }) { AppRegistry.registerComponent('main', () => Main); const { getStyleElement } = AppRegistry.getApplication('main'); const page = await renderPage(); const styles = [ <style key="react-native-style" dangerouslySetInnerHTML={{ __html: style }} />, getStyleElement(), ]; return { ...page, styles: Children.toArray(styles) }; } render() { return ( <Html style={{ height: '100%' }}> <Head /> <body style={{ height: '100%', overflow: 'hidden' }}> <Main /> <NextScript /> </body> </Html> ); } }
pages/_app.js
import Head from 'next/head'; export default function App({ Component, pageProps }) { return ( <> <Head> <meta name="viewport" content="width=device-width, initial-scale=1" /> </Head> <Component {...pageProps} /> </> ); }

转换模块

🌐 Transpiling modules

默认情况下,React Native 生态系统中的模块不会被转译以在网页浏览器中运行。React Native 依赖于 Metro 的高级缓存机制来实现快速重载。Next.js 使用 webpack,而 webpack 没有相同级别的缓存,因此默认情况下没有节点模块会被转译。你需要在 next.config.js 中手动使用 transpilePackages 选项标记每个想要转译的模块:

🌐 By 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:

next.config.js
const { withExpo } = require('@expo/next-adapter'); module.exports = withExpo({ experimental: { transpilePackages: [ // NOTE: Even though `react-native` is never used in Next.js, // you need to list `react-native` because `react-native-web` // is aliased to `react-native`. 'react-native', 'react-native-web', 'expo', // Add more React Native/Expo packages here... ], }, });

部署到 Vercel

🌐 Deploy to Vercel

这是 Vercel 推荐的将 Next.js 项目部署到生产环境的方法。

🌐 This is Vercel's preferred method for deploying Next.js projects to production.

1

在你的 package.json 中添加一个 build 脚本:

🌐 Add a build script to your package.json:

package.json
{ "scripts": { "build": "next build" } }

2

安装 Vercel CLI:

🌐 Install the Vercel CLI:

Terminal
npm i -g vercel

3

部署到 Vercel:

🌐 Deploy to Vercel:

Terminal
vercel

与默认 Web 版 Expo 相比的限制或差异

🌐 Limitations or differences compared to the default Expo for Web

在网页开发中使用 Next.js 意味着你将使用 Next.js 的 webpack 配置进行打包。这会导致你在开发应用与网站时的一些核心差异。

🌐 Using 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.

  • Expo Next.js 适配器不支持实验性的 app 目录。
  • 对于原生的基于文件的路由,我们推荐使用 Expo Router

贡献

🌐 Contributing

如果你想帮助改善 Expo 中的 Next.js 支持,请随时打开 PR 或提交问题:

🌐 If you would like to help make Next.js support in Expo better, feel free to open a PR or submit an issue:

故障排除

🌐 Troubleshooting

不能在模块外部使用 import 语句

🌐 Cannot use import statement outside a module

找出哪个模块有 import 语句,并将其添加到 next.config.js 中的 transpilePackages 选项:

🌐 Figure out which module has the import statement and add it to the transpilePackages option in next.config.js:

next.config.js
const { withExpo } = require('@expo/next-adapter'); module.exports = withExpo({ experimental: { transpilePackages: [ 'react-native', 'react-native-web', 'expo', // Add the failing package here, and restart the server... ], }, });