服务器渲染
了解如何使用服务器端渲染(SSR)在请求时动态渲染 Expo Router 路由。
For the complete documentation index, see llms.txt. Use this file to discover all available pages.
服务器端渲染(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:
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "web": { "output": "server" }, "plugins": [ [ "expo-router", { "unstable_useServerRendering": true } ] ] } }
2
启动开发服务器:
🌐 Start the development server:
- npx expo start生产
🌐 Production
要将你的应用导出用于生产环境,请运行导出命令:
🌐 To export your app for production, run the export command:
- 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:
distclient_expostaticjswebentry-[hash].jscss[name]-[hash].cssserver_exporoutes.jsonserverrender.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:
- 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.
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/html 的 useServerDocumentContext 钩子提供了服务器渲染器注入到文档中的元数据和资源节点。你必须将这些值扩展到你的 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 可能会出现损坏,或者你的应用可能无法正常工作。
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运行 - 它不会在客户端重新水化,并且应该只使用
useServerDocumentContextReact 钩子 - 你不能在
+html.tsx中导入全局 CSS(请使用 Root Layout 来添加样式) - 你不能在你的
+html.tsx中调用像window或document这样的浏览器 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:
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/Express | expo-server/adapter/express |
| Cloudflare Workers | expo-server/adapter/workerd |
| Vercel Edge 功能 | expo-server/adapter/vercel |
| Netlify Edge 功能 | expo-server/adapter/netlify |
| Bun | expo-server/adapter/bun |
示例:使用 EAS 托管进行部署
EAS Hosting 开箱即支持服务器端渲染。导出你的应用并使用以下方式部署:
🌐 EAS Hosting supports server rendering out of the box. Export your app and deploy with:
- 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.