将 Next.js 与 Expo 一起用于 Web

编辑页面

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


For the complete documentation index, see llms.txt. 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 项目
  • Web: npx 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', // 在这里添加失败的包,并重启服务器... ], }, });