服务端渲染

服务器端渲染(SSR)是指按需在服务器上生成 HTML 页面并将其发送到客户端。

SSR 允许你:

  • 在应用中实现登录状态的会话。
  • 从使用 fetch 动态调用的 API 呈现数据。
  • 使用 适配器 将站点部署到主机。

如果需要,请考虑在 Astro 项目中启用服务器端渲染:

  • API 端点:SSR 使你能够创建特定页面,这些页面充当数据库访问、身份验证和授权等任务的 API 端点,同时对客户端隐藏敏感数据。
  • 受保护的页面:如果需要根据用户权限限制对页面的访问,可以启用 SSR 来处理服务器上的用户访问。
  • 频繁更改的内容:启用 SSR 可让你生成单个页面,而无需静态重建站点。当页面内容频繁更新时,这很有用。

首先,在开发模式使用output: server配置选项来启用 SSR 功能: 要为生产部署启用 SSR 功能,请将 输出 配置更新为 服务器混合(在 v2.6.0 中引入)。这两种模式都控制哪些页面服务器端点应该呈现服务器。每个配置选项都有不同的默认行为,并允许各个路由相应地选择退出默认值:

  • output: 'server':默认情况下服务器渲染。当大部分或全部网站应由服务器渲染时,请使用此选项。任何单个页面或端点都可以选择加入预渲染。
  • output: 'hybrid':默认预渲染为 HTML。当网站的大部分内容应该是静态的时,请使用此选项。任何单个页面或端点都可以选择退出预渲染。
astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
output: 'server'
});

然后,你可以在任何页面或路由中使用 export 语句选择退出默认渲染行为:

src/pages/mypage.astro
---
export const prerender = true;
// ...
---
<html>
<!-- 静态的,预渲染的页面在这里... -->
</html>

查看更多配置单个路由的使用示例。

将静态网站转换为混合渲染

标题部分 将静态网站转换为混合渲染

要将现有的静态 Astro 站点转换为允许混合渲染,请将 output 更改为 hybrid 并添加适配器:

import { defineConfig } from 'astro/config';
import nodejs from '@astrojs/node';
export default defineConfig({
adapter: nodejs(),
output: 'hybrid',
});

当需要部署 SSR 项目时,你还需要添加一个适配器。这是因为 SSR 需要一个服务器 运行时:运行服务器端代码的环境。每个适配器都允许 Astro 输出在特定运行时运行项目的脚本。

现有以下适配器,未来将有更多适配器:

你可以使用以下 astro add 命令添加任何官方适配器。这将安装一步适配器并对你的 astro.config.mjs 文件进行适当的更改。 例如,要安装 Netlify 适配器,请运行:

终端窗口
npx astro add netlify

你还可以通过自己手动安装软件包和更新 astro.config.mjs 来手动添加一个适配器。(请参阅上面的链接以获取特定于适配器的说明,以完成以下两个步骤以启用 SSR。)使用 my-adapter 作为示例占位符,说明将类似于:

  1. 使用你首选的包管理器将适配器安装到你的项目依赖项:

    终端窗口
    npm install @astrojs/my-adapter
  2. 添加一个适配器到你的 astro.config.mjs 文件的导入和默认导出:

    astro.config.mjs
    import { defineConfig } from 'astro/config';
    import myAdapter from '@astrojs/my-adapter';
    export default defineConfig({
    output: 'server',
    adapter: myAdapter(),
    });

Astro 在默认情况下仍是静态网站生成器。但是一旦你启用了服务器端渲染适配器,你的页面目录中的每条路由都会成为服务器渲染的路由,一些新的功能就可以为你所用。

serverhybrid 模式都允许服务器渲染的页面和端点,并将根据其默认模式渲染。但是,这两种模式都允许你将单个页面标记为 opt-out 此默认行为。

选择退出服务端渲染

标题部分 选择退出服务端渲染

对于配置为 output: server 的服务器渲染的应用,请将 export const prerender = true 添加到任何页面或路由以预渲染静态页面或端点:

src/pages/mypage.astro
---
export const prerender = true;
// ...
---
<html>
<!-- 静态的,预渲染的页面在这里... -->
</html>
src/pages/mypage.mdx
---
layout: '../layouts/markdown.astro'
title: 'My page'
---
export const prerender = true;
# 这是我的静态预渲染页面

对于端点:

src/pages/myendpoint.js
export const prerender = true;
export async function get() {
return {
body: JSON.stringify({ message: `这是我的静态端点` }),
};
}

对于配置为 output: hybrid 的静态站点,将 export const prerender = false 添加到应由服务器渲染的任何文件中:

src/pages/randomnumber.js
export const prerender = false;
export async function get() {
let number = Math.random();
return {
body: JSON.stringify({ number, message: `这是一个随机数: ${number}` }),
};
}

请求的标头可在 Astro.request.headers 上找到。这就像浏览器的 Request.headers。它是一个 Headers 对象,一个类似 Map 的对象,你可以在其中检索标头,例如 cookie。

src/pages/index.astro
---
const cookie = Astro.request.headers.get('cookie');
// ...
---
<html>
<!-- 你的页面 -->
</html>

The HTTP method used in the request is available as Astro.request.method. This works like the browser’s Request.method. It returns the string representation of the HTTP method used in the request. 请求中使用的 HTTP 方法可用作 Astro.request.method。这就像浏览器的 Request.method 一样。它返回请求中使用的 HTTP 方法的字符串表示形式。

src/pages/index.astro
---
console.log(Astro.request.method) // GET (在浏览器中导航到时)
---

这是一个用于读取和修改单个 cookie 的工具方法。 它允许你检查、设置、获取和删除 cookie。

在 API 参考中查看有关 Astro.cookiesAstroCookie 类型的更多详细信息。

下面的示例更新了页面显示的计数器的 cookie 值。

src/pages/index.astro
---
let counter = 0
if(Astro.cookies.has("counter")){
const cookie = Astro.cookies.get("counter")
counter = cookie.number() + 1
}
Astro.cookies.set("counter",counter)
---
<html>
<h1>Counter = {counter}</h1>
</html>

任何页面都可以返回 Response。可以在数据库中查找 ID 后,再动态返回 404 页面。

src/pages/[id].astro
---
import { getProduct } from '../api';
const product = await getProduct(Astro.params.id);
// 没有找到任何记录
if(!product) {
return new Response(null, {
status: 404,
statusText: 'Not found'
});
}
---
<html>
<!-- 你的页面 -->
</html>

服务器端点,也称为 API 路由,是从 src/pages/ 文件夹中的 .js.ts 文件导出的特殊函数。 该函数接收一个端点上下文对象并返回一个 Response。 这是 SSR 的一个强大功能,API 路由能够在服务器上安全地执行代码。 要了解更多信息,请参阅我们的端点指南

浏览器本身支持 HTTP 流,其中文档被分解为块,按顺序通过网络发送,并按该顺序呈现在页面上。

在此过程中,浏览器以增量方式使用 HTML:解析、呈现到 DOM 中和绘制。无论你是否有意流式传输 HTML,都会发生这种情况。网络状况可能会导致大型文档下载缓慢,等待数据获取可能会阻止页面呈现。

使用流式处理提高页面性能

标题部分 使用流式处理提高页面性能

下面 await 其 frontmatter 中的一些数据。Astro 将等待所有 fetch 调用解析,然后再将任何 HTML 发送到浏览器。

src/pages/index.astro
---
const personResponse = await fetch('https://randomuser.me/api/');
const personData = await personResponse.json();
const randomPerson = personData.results[0];
const factResponse = await fetch('https://catfact.ninja/fact');
const factData = await factResponse.json();
---
<html>
<head>
<title>一个名字和一个事实</title>
</head>
<body>
<h2>一个名字</h2>
<p>{randomPerson.name.first}</p>
<h2>一个事实</h2>
<p>{factData.fact}</p>
</body>
</html>

await 调用移动到较小的组件中,你可以利用 Astro 的流式传输。使用以下组件执行数据获取,Astro 可以先渲染一些 HTML,例如标题,然后在数据准备就绪时呈现段落。

src/components/RandomName.astro
---
const personResponse = await fetch('https://randomuser.me/api/');
const personData = await personResponse.json();
const randomPerson = personData.results[0];
---
<p>{randomPerson.name.first}</p>
src/components/RandomFact.astro
---
const factResponse = await fetch('https://catfact.ninja/fact');
const factData = await factResponse.json();
---
<p>{factData.fact}</p>

下面使用这些组件的 Astro 页面可以更快地呈现页面的某些部分。数据提取不再阻止 <head><body><h1> 标记。然后,服务器将并行获取 RandomNameRandomFact 的数据,并将生成的 HTML 流式传输到浏览器。

src/pages/index.astro
---
import RandomName from '../components/RandomName.astro'
import RandomFact from '../components/RandomFact.astro'
---
<html>
<head>
<title>一个名字和一个事实</title>
</head>
<body>
<h2>一个名字</h2>
<RandomName />
<h2>一个事实</h2>
<RandomFact />
</body>
</html>

你还可以直接在模板中包含 promises。它不会阻止整个组件,而是并行解析 promise,并且只阻止它后面的标记。

src/pages/index.astro
---
const personPromise = fetch('https://randomuser.me/api/')
.then(response => response.json())
.then(arr => arr[0].name.first);
const factPromise = fetch('https://catfact.ninja/fact')
.then(response => response.json())
.then(factData => factData.fact);
---
<html>
<head>
<title>一个名字和一个事实</title>
</head>
<body>
<h2>一个名字</h2>
<p>{personPromise}</p>
<h2>一个事实</h2>
<p>{factPromise}</p>
</body>
</html>

在此示例中,一个名称将在加载 personPromisefactPromise 时渲染。 一旦 personPromise 解决,一个事实就会出现,factpromise 将在加载完成后呈现。

将 SSR 构建拆分为多个文件

标题部分 将 SSR 构建拆分为多个文件

默认情况下,Astro 将所有页面构建到一个名为 entry.mjs 的单个文件中。你可以在构建选项中配置 split,改为为每个页面发出一个文件:

astro.config.mjs
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
build: {
split: true
}
});

发出的页面文件将保存在为 outDir 指定的目录中,在新创建的 pages/ 目录下。

构建的 pages/ 目录中的文件将镜像 src/pages/ 中页面文件的目录结构,例如:

  • 目录dist/
    • 目录pages/
      • 目录blog/
        • entry._slug_.astro.mjs
        • entry.about.astro.mjs
      • entry.index.astro.mjs