将 Next.js 与 Expo 一起用于 Web

编辑页面

将 Next.js 与 Expo 集成用于 Web 的指南。


For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.

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

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

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

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

自动设置

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

Terminal
npx create-expo-app -e with-nextjs
  • 原生npx expo start — 启动 Expo 项目
  • Webnpx next dev — 启动 Next.js 项目

手动设置

安装依赖

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

Terminal
yarn add expo next @expo/next-adapter

转译

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

带 swc 的 Next.js。(推荐)

推荐使用带 SWC 的 Next.js。你可以配置 babel.config.js 只针对原生端:

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

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

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

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

babel.config.js
module.exports = function (api) { // 检测 Web 的使用情况(如果 Next.js 更改了加载器,这一点将来可能会变化) const isWeb = api.caller( caller => caller && (caller.name === 'babel-loader' || caller.name === 'next-babel-turbo-loader') ); return { presets: [ // 仅在浏览器中使用 next,它会破坏你的原生项目 isWeb && require('next/babel'), 'babel-preset-expo', ].filter(Boolean), }; };

Next.js 配置

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

next.config.js
const { withExpo } = require('@expo/next-adapter'); module.exports = withExpo({ // transpilePackages 是 Next.js +13.1 的功能。 // 较旧版本可以使用 next-transpile-modules transpilePackages: [ 'react-native', 'react-native-web', 'expo', // 在这里添加更多 React Native/Expo 包... ], });

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

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', // 在这里添加更多 React Native/Expo 包... ], experimental: { forceSwcTransforms: true, }, }); module.exports = nextConfig;

React Native Web 样式

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

pages/_document.js
import { Children } from 'react'; import Document, { Html, Head, Main, NextScript } from 'next/document'; import { AppRegistry } from 'react-native'; // 遵循 react-native-web 的设置: // https://necolas.github.io/react-native-web/docs/setup/#root-element // 另外还为各种浏览器添加了额外的 React Native 滚动和文本一致性样式。 // 强制 Next 生成的 DOM 元素填满其父元素的高度 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 { /* 允许你滚动到视口下方;默认值是 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} /> </> ); }

模块转译

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

next.config.js
const { withExpo } = require('@expo/next-adapter'); module.exports = withExpo({ experimental: { transpilePackages: [ // 注意:即使 `react-native` 从未在 Next.js 中使用, // 你也需要列出 `react-native`,因为 `react-native-web` // 被映射到了 `react-native`。 'react-native', 'react-native-web', 'expo', // 在这里添加更多 React Native/Expo 包... ], }, });

部署到 Vercel

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

1

build 脚本添加到你的 package.json

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

2

安装 Vercel CLI:

Terminal
npm i -g vercel

3

部署到 Vercel:

Terminal
vercel

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

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

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

贡献

如果你想帮助改进 Expo 对 Next.js 的支持,欢迎提交 PR 或 issue:

故障排查

无法在模块外部使用 import 语句

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

next.config.js
const { withExpo } = require('@expo/next-adapter'); module.exports = withExpo({ experimental: { transpilePackages: [ 'react-native', 'react-native-web', 'expo', // 在这里添加失败的包,并重启服务器... ], }, });