服务器渲染

了解如何使用服务器端渲染(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 应用启用服务器渲染。

🌐 Server-side rendering (SSR) generates HTML dynamically on each request, as opposed to static rendering, which pre-renders HTML at build time. This guide walks you through enabling server rendering for your Expo Router app.

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

设置

🌐 Setup

1

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

🌐 Enable server rendering in your project's app config:

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

2

启动开发服务器:

🌐 Start the development server:

Terminal
npx expo start

生产

🌐 Production

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

🌐 To export your app for production, run the export command:

Terminal
npx expo export --platform web

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

🌐 This creates a dist directory with your server-rendered application. Unlike static rendering, no HTML files are pre-generated. Instead, the output includes a similar directory structure as shown below:

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

上面的输出包括 dist 目录下的以下目录:

🌐 In output above includes the following directories inside dist directory:

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

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

🌐 You can test the production build locally by running the following command and opening the linked URL in your browser:

Terminal
npx expo serve

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

🌐 The above command starts a local server that renders pages on each request, simulating a production environment.

动态路由

🌐 Dynamic routes

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

🌐 With server rendering, dynamic routes are rendered on the fly, and the generateStaticParams export is not needed and should be removed. If your route file exports generateStaticParams, those routes will be handled dynamically instead. The route is rendered at request time with the actual parameters from the 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>Post {id}</Text>; }

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

🌐 In the above example, when the app user visits /blog/my-post, the page is rendered on the server with id set to "my-post".

根 HTML

🌐 Root HTML

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

🌐 You can customize the root HTML document by creating a src/app/+html.tsx file. This component wraps all routes and runs only on the server.

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

🌐 The useServerDocumentContext hook from expo-router/html provides metadata and asset nodes that the server renderer injects into the document. You must spread these values into your HTML to ensure metadata, fonts, and CSS are included in the response:

  • 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'; // This file is web-only and used to configure the root HTML for every // web page during server rendering. // The contents of this function only run in Node.js environments and // do not have access to the DOM or browser APIs. 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" /> {/* Disable body scrolling on web. This makes ScrollView components work closer to how they do on native platforms. However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line. */} <ScrollViewStyleReset /> {headNodes} {/* Add any additional <head> elements that you want globally available on web... */} </head> <body {...bodyAttributes}> {children} {bodyNodes} </body> </html> ); }

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

🌐 The +html.tsx file is only used by the server renderer and never by client code. This means:

  • 它将在服务器渲染期间由 expo-server 运行
  • 它不会在客户端重新水化,并且应该只使用 useServerDocumentContext React 钩子
  • 你不能在 +html.tsx 中导入全局 CSS(请使用 Root Layout 来添加样式)
  • 你不能在你的 +html.tsx 中调用像 windowdocument 这样的浏览器 API

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

🌐 All +html.tsx components are expected to render the children prop they receive in their JSX content.

元数据

🌐 Metadata

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

🌐 Routes may export a generateMetadata function to define per-page metadata such as title, description, and Open Graph tags. This function runs on the server before rendering begins, and its result is injected into the <head> of the HTML document via the headNodes provided by useServerDocumentContext in your Root HTML component.

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

🌐 Export a generateMetadata function from your route file and return a Metadata object. The function receives the incoming request and route parameters, which you can use to generate metadata dynamically:

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>Post {id}</Text>; }

generateMetadata 函数在服务器上执行,并从客户端包中去除,类似于 数据加载器。有关支持的元数据字段的完整列表,请参阅 expo-server API 参考中的 Metadata 类型。

🌐 The generateMetadata function executes on the server and is stripped from the client bundle, similar to data loaders. For a full list of supported metadata fields, see the Metadata type in the expo-server API reference.

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

🌐 Using <Head> with server rendering

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

🌐 You can also use the <Head> component from expo-router/head to add <meta> tags. Both approaches can co-exist in the same route. However, generateMetadata is the recommended approach for server rendering because it resolves metadata before the HTML stream begins, ensuring that <meta> tags are included in the earliest bytes of the response. <Head> can be used to update <meta> tags dynamically after the app has hydrated.

部署

🌐 Deployment

服务端渲染需要一个运行时服务器来在每次请求时渲染页面。使用服务器端渲染的 Expo 应用无法部署到像 GitHub Pages 这样的静态托管服务。

🌐 Server-side rendering requires a runtime server to render pages on each request. Server-side rendered Expo apps cannot be deployed to static hosting services like GitHub Pages.

支持的平台

🌐 Supported platforms

平台适配器
EAS 托管内置
Node.js/Expressexpo-server/adapter/express
Cloudflare Workersexpo-server/adapter/workerd
Vercel Edge 功能expo-server/adapter/vercel
Netlify Edge 功能expo-server/adapter/netlify
Bunexpo-server/adapter/bun
示例:使用 EAS 托管进行部署

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

🌐 EAS Hosting supports server rendering out of the box. Export your app and deploy with:

Terminal
npx expo export --platform web

npx eas-cli@latest hosting:deploy dist

与静态渲染的比较

🌐 Comparison with static rendering

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

常见问题

🌐 Common questions

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

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

🌐 Yes. Server rendering works with data loaders to fetch data on the server before rendering.

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

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

🌐 Currently, Expo Router does not support mixing server and static rendering in the same project. Choose a single output mode based on your requirements.

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

缓存由服务器或CDN级别处理。配置你的部署平台,根据URL模式或缓存头来缓存响应。

🌐 Caching is handled at the server or CDN level. Configure your deployment platform to cache responses based on URL patterns or cache headers.

服务器渲染可以与 API 路由一起使用吗?

是的。API 路由 与渲染模式无关。它们始终在服务器上执行。

🌐 Yes. API routes work independently of the rendering mode. They are always executed on the server.