服务器渲染

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


重要 服务器渲染目前处于 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.

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

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

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

app/+html.tsx
import { ScrollViewStyleReset } from 'expo-router/html'; import { type PropsWithChildren } 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 }: PropsWithChildren) { return ( <html lang="en"> <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 /> {/* Add any additional <head> elements that you want globally available on web... */} </head> <body>{children}</body> </html> ); }

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

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

  • 它将在服务器渲染期间由 expo-server 运行
  • 它不会在客户端重新水化,因此不应该使用 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.

元标签

🌐 Meta tags

使用 expo-router 中的 <Head /> 组件为你的页面添加元标签:

🌐 Add meta tags to your pages using the <Head /> component from expo-router:

app/about.tsx
import Head from 'expo-router/head'; import { Text } from 'react-native'; export default function Page() { return ( <> <Head> <title>About Us</title> <meta name="description" content="Learn more about our company." /> </Head> <Text>About page content</Text> </> ); }

在服务器端渲染期间,<Head> 元素会被提取并包含在初始 HTML 响应中,同时修改发送给客户端的 <head> 元素,从而提高搜索引擎优化(SEO)效果。

🌐 During server-side rendering, <Head> elements are extracted and included in the initial HTML response, modifying the <head> element sent to the client, and improving search engine optimization (SEO).

部署

🌐 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 Functionsexpo-server/adapter/vercel
Netlify Edge Functionsexpo-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

FeatureStatic RenderingServer Rendering
HTML generationBuild timeRequest time
Configurationweb.output: 'static'web.output: 'server'
Dynamic routesRequires generateStaticParamsWorks automatically
Server requiredNoYes
Time to First ByteFastest (cached)Slower (rendered per request)
HostingAny static hostServer runtime required

常见问题

🌐 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.