服务器渲染
编辑页面
了解如何使用服务器端渲染(SSR)在请求时动态渲染 Expo Router 路由。
For the complete documentation index, see llms.txt. Use this file to discover all available pages.
服务器端渲染(SSR)会在每次请求时动态生成 HTML,而 静态渲染 则是在构建时预渲染 HTML。本指南将引导你为 Expo Router 应用启用服务器渲染。
信息 使用服务端渲染时,数据加载器 会在服务器上针对每次请求执行,其结果会嵌入到 HTML 响应中。
设置
1
在项目的应用配置中启用服务器渲染:
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "web": { "output": "server" }, "plugins": [ [ "expo-router", { "unstable_useServerRendering": true } ] ] } }
2
启动开发服务器:
- npx expo start生产环境
要为生产环境导出你的应用,请运行导出命令:
- npx expo export --platform web这会创建一个包含服务器渲染应用的 dist 目录。与静态渲染不同,不会预先生成 HTML 文件。相反,输出会包含如下所示的类似目录结构:
distclient_expostaticjswebentry-[hash].jscss[name]-[hash].cssserver_exporoutes.jsonserverrender.js上面的输出在 dist 目录中包含以下目录:
- client 目录:包含用于客户端 hydration 的 JavaScript 和 CSS bundles
- server 目录:包含路由清单和服务器渲染模块
你可以通过运行以下命令并在浏览器中打开链接的 URL 来本地测试生产构建:
- npx expo serve上述命令会启动一个本地服务器,在每次请求时渲染页面,模拟生产环境。
动态路由
使用服务器渲染时,动态路由会即时渲染,因此不需要 generateStaticParams 导出,并且应当移除。如果你的路由文件导出了 generateStaticParams,这些路由将改为动态处理。路由会在请求时使用 URL 中的实际参数进行渲染。
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/html 的 useServerDocumentContext hook 提供了服务器渲染器注入到文档中的元数据和资源节点。你必须将这些值展开到 HTML 中,以确保响应中包含元数据、字体和 CSS:
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'; // 此文件仅适用于 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,并且只能使用
useServerDocumentContextReact hook - 你不能在
+html.tsx中导入全局 CSS(请使用 Root Layout 来处理样式) - 你不能在
+html.tsx中调用window或document等浏览器 API
所有 +html.tsx 组件都应在其 JSX 内容中渲染它们接收到的 children 属性。
元数据
路由可以导出一个 generateMetadata 函数,用于定义每个页面的元数据,例如 title、description 和 Open Graph 标签。该函数在服务器上于渲染开始前运行,其结果会通过 Root HTML 组件中 useServerDocumentContext 提供的 headNodes 注入到 HTML 文档的 <head> 中。
从你的路由文件中导出一个 generateMetadata 函数,并返回一个 Metadata 对象。该函数会接收传入的请求和路由参数,你可以利用它们动态生成元数据:
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/Express | expo-server/adapter/express |
| Cloudflare Workers | expo-server/adapter/workerd |
| Vercel Edge Functions | expo-server/adapter/vercel |
| Netlify Edge Functions | expo-server/adapter/netlify |
| Bun | expo-server/adapter/bun |
示例:使用 EAS Hosting 部署
EAS Hosting 开箱即支持服务器渲染。导出你的应用并使用以下命令部署:
- npx expo export --platform web- npx eas-cli@latest hosting:deploy dist与静态渲染的比较
| 功能 | 静态渲染 | 服务器渲染 | |
|---|---|---|---|
| HTML 生成 | 构建时 | 请求时 | |
| HTML 传递 | 完整文档 | 逐步流式传输 | |
| 配置 | web.output: 'static' | web.output: 'server' | |
| 动态路由 | 需要 generateStaticParams | 自动生效 | |
| 元数据 | <Head> 组件 | generateMetadata | |
| 需要服务器 | |||
| 首字节时间 | 最快(已缓存) | 较慢(每次请求渲染) | |
| 托管 | 任何静态托管服务 | 需要服务器运行时 |
常见问题
我可以在服务器渲染中使用数据加载器吗?
可以。服务器渲染可与数据加载器配合使用,在渲染之前先在服务器上获取数据。
服务器渲染支持 API 路由吗?
可以。API 路由 与渲染模式相互独立。它们始终在服务器上执行。