服务器渲染

编辑页面

了解如何使用服务器端渲染(SSR)在请求时动态渲染 Expo Router 路由。


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

重要 服务器渲染处于 alpha 阶段,并可在 SDK 55 及更高版本中使用。生产环境使用需要一个已部署的服务器

服务器端渲染(SSR)会在每次请求时动态生成 HTML,而 静态渲染 则是在构建时预渲染 HTML。本指南将引导你为 Expo Router 应用启用服务器渲染。

信息 使用服务端渲染时,数据加载器 会在服务器上针对每次请求执行,其结果会嵌入到 HTML 响应中。

设置

1

在项目的应用配置中启用服务器渲染:

app.json
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "web": { "output": "server" }, "plugins": [ [ "expo-router", { "unstable_useServerRendering": true } ] ] } }

2

启动开发服务器:

Terminal
npx expo start

生产环境

要为生产环境导出你的应用,请运行导出命令:

Terminal
npx expo export --platform web

这会创建一个包含服务器渲染应用的 dist 目录。与静态渲染不同,不会预先生成 HTML 文件。相反,输出会包含如下所示的类似目录结构:

dist
client
  _expo
   static
    js
     web
      entry-[hash].js
    css
     [name]-[hash].css
server
  _expo
   routes.json
   server
    render.js

上面的输出在 dist 目录中包含以下目录:

  • client 目录:包含用于客户端 hydration 的 JavaScript 和 CSS bundles
  • server 目录:包含路由清单和服务器渲染模块

你可以通过运行以下命令并在浏览器中打开链接的 URL 来本地测试生产构建:

Terminal
npx expo serve

上述命令会启动一个本地服务器,在每次请求时渲染页面,模拟生产环境。

动态路由

使用服务器渲染时,动态路由会即时渲染,因此不需要 generateStaticParams 导出,并且应当移除。如果你的路由文件导出了 generateStaticParams,这些路由将改为动态处理。路由会在请求时使用 URL 中的实际参数进行渲染。

src/app/blog/[id].tsx
import { Text } from 'react-native'; import { useLocalSearchParams } from 'expo-router'; export default function Page() { const { id } = useLocalSearchParams(); return <Text>帖子 {id}</Text>; }

在上面的示例中,当应用用户访问 /blog/my-post 时,该页面会在服务器上渲染,且 id 被设置为 "my-post"

根 HTML

你可以通过创建 src/app/+html.tsx 文件来自定义根 HTML 文档。此组件会包裹所有路由,并且只在服务器上运行。

来自 expo-router/htmluseServerDocumentContext hook 提供了服务器渲染器注入到文档中的元数据和资源节点。你必须将这些值展开到 HTML 中,以确保响应中包含元数据、字体和 CSS:

  • htmlAttributes:要添加到 <html> 元素的属性
  • bodyAttributes:要添加到 <body> 元素的属性
  • headNodes:用于 <head> 元素的 React 节点(元数据、CSS 和其他资源)
  • bodyNodes:用于 <body> 元素的 React 节点(字体和其他延迟加载的资源)

信息 在创建自定义 +html.tsx 模板时,你必须使用 useServerDocumentContext 返回给你的所有属性。否则,你的服务端渲染 HTML 可能会显示异常,或者应用可能无法正常工作。

src/app/+html.tsx
import { ScrollViewStyleReset, useServerDocumentContext } from 'expo-router/html'; import type { ReactNode } from 'react'; // 此文件仅适用于 web,并用于在服务器渲染期间为每个 // web 页面配置根 HTML。 // 此函数中的内容仅在 Node.js 环境中运行,并且 // 无法访问 DOM 或浏览器 API。 export default function Root({ children }: { children: ReactNode }) { const { bodyAttributes, bodyNodes, htmlAttributes, headNodes } = useServerDocumentContext(); return ( <html lang="en" {...htmlAttributes}> <head> <meta charSet="utf-8" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> {/* 禁用 web 上的 body 滚动。这会让 ScrollView 组件的行为更接近它们在原生平台上的表现。 不过,对于移动端 web 来说,body 滚动通常也很有用。如果你想启用它,请移除这一行。 */} <ScrollViewStyleReset /> {headNodes} {/* 添加任何你希望在 web 上全局可用的额外 <head> 元素... */} </head> <body {...bodyAttributes}> {children} {bodyNodes} </body> </html> ); }

+html.tsx 文件仅由服务器渲染器使用,客户端代码永远不会使用它。这意味着:

  • 它会在服务器渲染期间由 expo-server 运行
  • 它不会在客户端重新 hydration,并且只能使用 useServerDocumentContext React hook
  • 你不能在 +html.tsx 中导入全局 CSS(请使用 Root Layout 来处理样式)
  • 你不能在 +html.tsx 中调用 windowdocument 等浏览器 API

所有 +html.tsx 组件都应在其 JSX 内容中渲染它们接收到的 children 属性。

元数据

路由可以导出一个 generateMetadata 函数,用于定义每个页面的元数据,例如 title、description 和 Open Graph 标签。该函数在服务器上于渲染开始前运行,其结果会通过 Root HTML 组件中 useServerDocumentContext 提供的 headNodes 注入到 HTML 文档的 <head> 中。

从你的路由文件中导出一个 generateMetadata 函数,并返回一个 Metadata 对象。该函数会接收传入的请求和路由参数,你可以利用它们动态生成元数据:

src/app/blog/[id].tsx
import { Text } from 'react-native'; import { useLocalSearchParams } from 'expo-router'; import type { GenerateMetadataFunction } from 'expo-router/server'; export const generateMetadata: GenerateMetadataFunction = async (request, params) => { const response = await fetch(`https://api.example.com/posts/${params.id}`); const post = await response.json(); return { title: post.title, description: post.excerpt, openGraph: { title: post.title, description: post.excerpt, images: post.coverImage, }, }; }; export default function BlogPost() { const { id } = useLocalSearchParams(); return <Text>帖子 {id}</Text>; }

generateMetadata 函数在服务器上执行,并会像 数据加载器 一样从客户端包中移除。支持的元数据字段完整列表请参见 expo-server API 参考中的 Metadata 类型。

在服务器渲染中使用 <Head>

你也可以使用 expo-router/head 中的 <Head> 组件来添加 <meta> 标签。这两种方式可以在同一路由中共存。不过,对于服务器渲染,推荐使用 generateMetadata,因为它会在 HTML 流开始之前解析元数据,确保 <meta> 标签包含在响应的最早字节中。<Head> 可用于在应用完成 hydration 后动态更新 <meta> 标签。

部署

服务器端渲染要求有一个运行时服务器来在每次请求时渲染页面。使用服务器端渲染的 Expo 应用不能部署到 GitHub Pages 之类的静态托管服务上。

支持的平台

平台适配器
EAS Hosting内置
Node.js/Expressexpo-server/adapter/express
Cloudflare Workersexpo-server/adapter/workerd
Vercel Edge Functionsexpo-server/adapter/vercel
Netlify Edge Functionsexpo-server/adapter/netlify
Bunexpo-server/adapter/bun
示例:使用 EAS Hosting 部署

EAS Hosting 开箱即支持服务器渲染。导出你的应用并使用以下命令部署:

Terminal
npx expo export --platform web

npx eas-cli@latest hosting:deploy dist

与静态渲染的比较

功能静态渲染服务器渲染
HTML 生成构建时请求时
HTML 传递完整文档逐步流式传输
配置web.output: 'static'web.output: 'server'
动态路由需要 generateStaticParams自动生效
元数据<Head> 组件generateMetadata
需要服务器
首字节时间最快(已缓存)较慢(每次请求渲染)
托管任何静态托管服务需要服务器运行时

常见问题

我可以在服务器渲染中使用数据加载器吗?

可以。服务器渲染可与数据加载器配合使用,在渲染之前先在服务器上获取数据。

我可以混合使用服务器渲染和静态渲染吗?

目前,Expo Router 不支持在同一个项目中混合使用服务器渲染和静态渲染。请根据你的需求选择一种输出模式。

如何缓存服务器渲染的响应?

缓存由服务器或 CDN 层处理。请根据 URL 模式或缓存头配置你的部署平台以缓存响应。

服务器渲染支持 API 路由吗?

可以。API 路由 与渲染模式相互独立。它们始终在服务器上执行。