Astro-路由导航
Astro 支持 src/pages/ 目录中的以下文件类型:
页面
页面是位于 Astro 项目的 src/pages/ 子目录中的文件。它们负责处理路由、数据加载以及网站中每个页面的整体页面布局。
支持的页面文件
Astro 支持 src/pages/ 目录中的以下文件类型:
基于文件的路由
Astro 使用一种名为 基于文件的路由 的路由策略。src/pages/ 目录中的每个文件都会根据其文件路径成为网站上的一个端点。
一个文件也可以使用动态路由来生成多个页面。这允许你即使内容不在特殊的 /pages/ 目录中,也可以创建页面,例如在内容集合或内容管理系统中。
页面之间的链接
在你的 Astro 页面中,使用标准的 HTML <a> 元素来链接到网站上的其他页面。这需要使用 相对于根域名的 URL 路径 作为你的链接,而不是相对文件路径。
例如,要从 example.com 上的任何其他页面链接到 https://example.com/authors/sonali/,你可以像这样:
src/pages/index.astro
阅读更多 <a href="/authors/sonali/">关于 Sonali 的信息</a>。Astro 页面
Astro 页面使用 .astro 文件扩展名,并支持与 Astro 组件相同的功能。
src/pages/index.astro
---
---
<html lang="en">
<head>
<title>我的主页</title>
</head>
<body>
<h1>欢迎来到我的网站</h1>
</body>
</html>一个页面必须生成完整的 HTML 文档。如果没有显式包含,Astro 将会自动为位于 src/pages/ 目录下的任何 .astro 组件默认添加必要的 <!DOCTYPE html> 声明和 <head> 内容。你可以通过将组件标记为局部页面来为每个组件避免这种行为。
为了避免在每个页面上重复相同的 HTML 元素,你可以将常见的 <head> 和 <body> 元素移动到自己的 布局组件 中。你可以使用任意多的布局组件。
src/pages/index.astro
---
import MySiteLayout from '../layouts/MySiteLayout.astro';
---
<MySiteLayout>
<p>包裹在 layout 里的页面内容!</p>
</MySiteLayout>Markdown/MDX 页面
Astro 还将 src/pages/ 目录中的任何 Markdown (.md) 文件视为网站中的页面。如果你安装了 MDX 集成,它也会将 MDX (.mdx) 文件视为相同的页面。
备注
当内容为博客文章或者产品项目这类共享相似结构的相关 Markdown 文件时,请考虑创建 内容集合 而不是页面。
Markdown 文件可以使用特殊的 layout 前置属性来指定一个 布局组件,它将包装其 Markdown 内容在一个完整的 <html>...</html> 页面文档中。
src/pages/page.md
---
layout: ../layouts/MySiteLayout.astro
title: 我的 Markdown 页面
---
# 标题
这是我的页面,使用 **Markdown** 编写。HTML 页面
.html 文件扩展名的文件可以放在 src/pages/ 中,并直接用作网站上的页面。请注意,某些关键的 Astro 功能在 HTML 组件 中不受支持。
自定义 404 错误页面
想要自定义 404 错误页面,你可以在 src/pages 中创建 404.astro 或 404.md 文件。
它将生成 404.html 页面。大多数部署服务都自动找到并使用它。
自定义 500 错误页面
要为按需渲染的页面创建自定义的 500 错误页面,可以创建一个 src/pages/500.astro 文件。但这个自定义页面不适用于预渲染的页面。
如果在渲染此页面时发生错误,将显示主机默认的 500 错误页面给访问者。
添加于: astro@4.10.3
在开发过程中,如果你有一个 500.astro 文件,运行时抛出的错误将被记录在终端中,而不是显示在错误覆盖层中。
error
添加于: astro@4.11.0
src/pages/500.astro 是一个特殊页面,它会在渲染过程中对抛出的任何错误都会自动传递一个 error 属性。这允许你向访客展示详细的错误信息(例如来自页面、中间件等)。
error 属性的数据类型可以是任何类型,这可能会影响你在代码中的定义或使用值的方式:
src/pages/500.astro
---
interface Props {
error: unknown;
}
const { error } = Astro.props;
---
<div>{error instanceof Error ? error.message : "Unknown error"}</div>为了避免在展示内容时从 error 属性中显示敏感信息,请首先考虑评估错误,并根据抛出的错误返回适当的内容。例如,应避免显示错误的堆栈,因为它包含有关服务器上代码结构的信息。
局部页面(未记录)
添加于: astro@3.4.0
路由
Astro 使用基于文件的路由,它根据项目 src/pages 目录中的文件结构来生成你的构建链接。
页面之间的导航
Astro 使用标准 HTML a 元素在路由之间导航。
src/pages/index.astro
<p>Read more <a href="/about/">about</a> Astro!</p>
<!-- 配置了 `base: "/docs"` -->
<p>Learn more in our <a href="/docs/reference/">reference</a> section!</p>静态路由
src/pages 目录中的 .astro 页面组件以及 Markdown 和 MDX 文件(.md,.mdx)将自动作为网站页面。每个页面的路由对应于它在 src/pages/ 目录中的路径和文件名。
# 示例:静态路由
src/pages/index.astro -> mysite.com/
src/pages/about.astro -> mysite.com/about
src/pages/about/index.astro -> mysite.com/about
src/pages/about/me.astro -> mysite.com/about/me
src/pages/posts/1.md -> mysite.com/posts/1Astro 项目没有单独的路由配置!当你在
src/pages目录新增文件时,会自动生成一个新的路由。在静态生成中,你可以使用build.format配置项自定义文件输出格式。
动态路由
Astro 页面文件可以在其文件名中指定动态路由参数以生成多个匹配页面。
例如,src/pages/authors/[author].astro 可以为你博客上的每一位作者生成一个简介页面,而 author 将作为一个你可以从页面内部访问的 参数 。
在 Astro 默认的静态输出模式下,这些页面会在构建时生成,因此你必须预先确定要生成页面的作者列表。 而在 SSR 模式 下,只要有请求匹配到对应路由,页面就会按需动态生成。
静态 (SSG) 模式
因为必须在构建时确定所有路由,所以动态路由必须导出一个 getStaticPaths() ,它返回一个具有 params 属性的对象数组。数组中的每一个对象都会生成相应的路由。
[dog].astro 在其文件名中定义一个 dog 参数,则 getStaticPaths() 返回的这些对象的 params 中必须包含 dog 。 然后页面可以使用 Astro.params 访问该参数。
src/pages/dogs/[dog].astro
---
export function getStaticPaths() {
return [
{ params: { dog: "clifford" }},
{ params: { dog: "rover" }},
{ params: { dog: "spot" }},
];
}
const { dog } = Astro.params;
---
<div>Good dog, {dog}!</div>这将生成三个页面: /dogs/clifford、/dogs/rover 和 /dogs/spot ,每个页面显示相应的狗名。
文件名可以包含多个参数,这些参数必须都包含在 getStaticPaths() 的 params 对象中:
src/pages/[lang]-[version]/info.astro
---
export function getStaticPaths() {
return [
{ params: { lang: "en", version: "v1" }},
{ params: { lang: "fr", version: "v2" }},
];
}
const { lang, version } = Astro.params;
---
...这将生成 /en-v1/info 和 /fr-v2/info 路由。
参数也可以包含在路径的单独部分中。例如, src/pages/[lang]/[version]/info.astro 文件与上面相同的 getStaticPaths() 将生成路由 /en/v1/info 和 /fr/v2/info。
解码 params
提供给 getStaticPaths() 函数的 params 不会被解码。当你需要解码参数值时,请使用 decodeURI 函数。
src/pages/[slug].astro
---
export function getStaticPaths() {
return [
{ params: { slug: decodeURI("%5Bpage%5D") }}, // 解码成 "[page]"
//这一步只是为了演示:如果你的参数中出现了 URL 编码内容(例如中文、特殊符号),需要先解码。
//构建时会生成一个静态页面: /[page]
]
}
---了解更多关于
getStaticPaths()的内容.
剩余参数
如果你的URL路由需要更加灵活,你可以在 .astro 文件名中使用一个 剩余参数([...path]),去匹配任何深度的文件路径。
src/pages/sequences/[…path].astro
---
export function getStaticPaths() {
return [
{ params: { path: "one/two/three" }},
{ params: { path: "four" }},
{ params: { path: undefined }}
]
}
const { path } = Astro.params;
---这将生成 /sequences/one/two/three 、 /sequences/four 和 /sequences 。(将剩余参数设置为 undefined 允许它匹配顶级页面。)
剩余参数可以与 其他命名参数 一起使用。例如, GitHub 的文件查看器可以用以下动态路由表示:
/[org]/[repo]/tree/[branch]/[...file]在这个示例中,对 /withastro/astro/tree/main/docs/public/favicon.svg 的请求将拆分为以下命名参数:
{
org: "withastro",
repo: "astro",
branch: "main",
file: "docs/public/favicon.svg"
}示例:多级动态页面
在下面的示例中, 剩余参数([...slug])和 getStaticPaths() 的 props 为不同深度的 slug 生成页面。
src/pages/[…slug].astro
---
export function getStaticPaths() {
const pages = [
{
slug: undefined,
title: "Astro Store",
text: "Welcome to the Astro store!",
},
{
slug: "products",
title: "Astro products",
text: "We have lots of products for you",
},
{
slug: "products/astro-handbook",
title: "The ultimate Astro handbook",
text: "If you want to learn Astro, you must read this book.",
},
];
return pages.map(({ slug, title, text }) => {
return {
params: { slug },
props: { title, text },
};
});
}
const { title, text } = Astro.props;
---
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
<p>{text}</p>
</body>
</html>按需动态路由
在使用适配器进行按需渲染时,动态路由的定义方式相同:在文件名中包含 [param] 或 [...path] ,用以匹配任意字符串或路径。
但由于这些路由不再在构建时提前生成,页面会对任何匹配的路由请求进行响应。
由于这些不是“静态”路由,因此不应使用 getStaticPaths。
对于按需渲染的路由,文件名中只能使用一个使用展开语法的剩余参数。 例如:
src/pages/[locale]/[...slug].astro
src/pages/[...locale]/[slug].astro是允许的; 但以下写法不允许:
src/pages/[...locale]/[...slug].astrosrc/pages/resources/[resource]/[id].astro
---
export const prerender = false; // 'server' 模式下,无需配置
const { resource, id } = Astro.params;
---
<h1>{resource}: {id}</h1>修改 [...slug] 示例用于SSR
由于 SSR 页面无法使用 getStaticPaths(),因此无法接收 props。可以通过在对象中查找 slug 参数的值,将 前面的示例 调整为 SSR 模式。如果路由位于根目录(“/”),则 slug 参数为 undefined。如果对象中不存在该值,将重定向到 404 页面。
src/pages/[…slug].astro
---
const pages = [
{
slug: undefined,
title: 'Astro Store',
text: 'Welcome to the Astro store!',
},
{
slug: 'products',
title: 'Astro products',
text: 'We have lots of products for you',
},
{
slug: 'products/astro-handbook',
title: 'The ultimate Astro handbook',
text: 'If you want to learn Astro, you must read this book.',
}
];
const { slug } = Astro.params;
const page = pages.find((page) => page.slug === slug);
if (!page) return Astro.redirect("/404");
const { title, text } = page;
---
<html>
<head>
<title>{title}</title>
</head>
<body>
<h1>{title}</h1>
<p>{text}</p>
</body>
</html>重定向
有时你需要将读者重定向到一个新页面,这可能是因为网站结构发生了变化(永久重定向),也可能是出于某种操作(例如登录后进入受保护的页面)。
备注
你可以在你的 Astro 配置中定义规则来将用户 永久重定向到已移动的页面。或者,根据用户的使用情况,动态地将用户重定向。
配置重定向
添加于: astro@2.9.0
在你的 Astro 配置中,你可以使用 redirects 值来指定永久重定向的映射关系。
对于内部重定向,这是旧路由路径到新路由的映射。从 Astro v5.2.0 开始,还可以重定向到以 http 或 https 开头的外部 URL,并且可以被解析:
astro.config.mjs
import { defineConfig } from "astro/config";
export default defineConfig({
redirects: {
"/old-page": "/new-page",
"/blog": "https://example.com/blog"
}
});这些重定向遵循与基于文件路由相同的优先级规则,并且优先级将会始终低于现有项目中同名的页面文件。例如:如果你的项目包含 src/pages/old-page.astro 文件,那么 /old-page 将不会重定向到 /new-page。
只要新旧路由都包含相同的参数,就可以使用动态路由,例如:
{
"/blog/[...slug]": "/articles/[...slug]"
}使用 SSR(服务器端渲染)或者静态适配器,你还可以将对象作为值提供,除了新的 destination 端点指向之外,还可以指定 status 状态码:
astro.config.mjs
import { defineConfig } from "astro/config";
export default defineConfig({
redirects: {
"/old-page": {
status: 302,
destination: "/new-page"
},
"/news": {
status: 302,
destination: "https://example.com/news"
}
}
});在运行 astro build 时,默认情况下,Astro 将输出带有 meta refresh 标签的 HTML 文件。支持的适配器将会使用托管服务器的配置文件写入重定向信息。
状态码默认为 301。如果构建为 HTML 文件,则服务器不会使用状态码。
动态重定向
在全局的 Astro 对象上,Astro.redirect 方法允许你动态地重定向到另一个页面。例如,你可以在检查用户是否已登录(通过从 Cookie 中获取其会话)之后执行此操作。
src/pages/account.astro
---
import { isLoggedIn } from "../utils";
const cookie = Astro.request.headers.get("cookie");
// 如果用户未登录,将其重定向到登录页面。
if (!isLoggedIn(cookie)) {
return Astro.redirect("/login");
}
---重要
由于 Astro 在按需渲染中使用 HTML 流式传输,重定向必须在页面级别完成,而不是在子组件中完成。
重写
添加于: astro@4.13.0
重写允许你提供不同的路由,而无需将浏览器重定向到不同的页面。浏览器将在 URL 栏中显示原始地址,但实际上会显示 Astro.rewrite() 提供的 URL 的内容。
对于永久性移动的内容,或者将用户重定向到具有新 URL 的不同页面(例如登录后的用户仪表盘),请使用 重定向。
重写对于在不同路径(例如 /products/shoes/men/ 和 /products/men/shoes/)显示相同内容非常有用,而无需维护两个不同的源文件。
重写对于用户体验和 SEO 也非常有用。它允许你显示需要将访问者重定向到其他页面或返回 404 状态的内容。 重写的一个常见用途是为语言的不同变体显示相同的本地化内容。
以下示例使用重写来在访问 /es-CU/(古巴西班牙语)URL 路径时渲染 /es/ 版本的页面。当访问者导航到 /es-cu/articles/introduction URL 时,Astro 将渲染由文件 src/pages/es/articles/introduction.astro 生成的内容。
src/pages/es-cu/articles/introduction.astro
---
return Astro.rewrite("/es/articles/introduction");
---使用 context.rewrite() 在你的端点文件中重定向到不同的页面:
src/pages/api.js
export function GET(context) {
if (!context.locals.allowed) {
return context.rewrite("/");
}
}如果传递给 Astro.rewrite() 的 URL 引发运行时错误,Astro 将在开发中显示错误覆盖层,并在生产中返回 500 状态码。如果你的项目中不存在该 URL,则将返回 404 状态码。
你可以有意创建一个重写来渲染你的 /404 页面,例如,以指示你的电子商店中的产品不再可用:
src/pages/[item].astro
---
const { item } = Astro.params;
if (!itemExists(item)) {
return Astro.rewrite("/404");
}
---你也可以根据 HTTP 响应状态有条件地重写,例如在访问不存在的 URL 时显示站点上的某个页面:
src/middleware.mjs
export const onRequest = async (context, next) => {
const response = await next();
if (response.status === 404) {
return context.rewrite("/");
}
return response;
}显示指定重写路径的内容之前,Astro.rewrite() 函数将触发一个新的完整渲染阶段。这将为新的路由/请求重新执行任何中间件。
有关更多信息,请查阅
Astro.rewrite()API 参考
路由优先级顺序
可能有多个已定义的路由试图构建相同的 URL 路径。例如,下面所有这些路由都有可能构建 /posts/create:

Astro 需要知道哪个路由应该被用来构建页面。为此,它会按照以下规则按顺序对它们进行排序:
- Astro 预留路由
- 路径段更多的路由将优先于不太具体的路由。在上面的例子中,所有在
/posts/下的路由优先于根目录的/[...slug].astro。 - 没有路径参数的静态路由将优先于动态路由。例如,
/posts/create.astro优先于示例中的所有其他路由。 - 使用命名参数的动态路由优先于剩余参数。例如,
/posts/[page].astro优先于/posts/[...slug].astro。 - 预渲染的动态路由优先于服务器动态路由。
- 端点优先于页面。
- 基于文件的路由优先于重定向。
- 如果以上规则都无法决定顺序,路由将根据你的 Node 安装的默认语言环境按字母顺序排序。
鉴于上面的示例,下面用几个例子,说明这些规则如何匹配请求的 URL 与用于建立 HTML 的路由。
pages/posts/create.astro- 将只构建/posts/createpages/posts/[pid].ts- 将构建/posts/abc、/posts/xyz等,但不包括/posts/createpages/posts/[page].astro- 将构建/posts/1、/posts/2等,但不包括/posts/create、/posts/abc以及/posts/xyzpages/posts/[...slug].astro- 将构建/posts/1/2、/posts/a/b/c等,但不包括/posts/create、/posts/1、/posts/abc等pages/[...slug].astro- 将构建/abc、/xyz、/abc/xyz等,但不包括/posts/create、/posts/1、/posts/abc等
预留路由
内部路由优先于任何用户定义或集成定义的路由,因为 Astro 功能需要它们才能工作。以下是 Astro 的预留路由:
_astro/: 向客户端提供所有静态资源,包括 CSS 文档、打包的客户端脚本、优化后的图像和任何 Vite 资源。_server_islands/: 为延迟渲染的 服务器群岛 动态组件提供服务。_actions/: 服务于任何已定义的 action。
分页
Astro 支持内置分页,用于需要分割成多个页面的大量数据。Astro 会生成常见的分页属性,包括上一页/下一页链接、总页数等。
分页的路由名应该使用与标准动态路由一样的 [bracket] 语法。例如,文件名 /astronauts/[page].astro 将生成 /astronauts/1、/astronauts/2 等路由,其中 [page] 是生成的页码。
你可以用 paginate() 函数根据数组值生成这些页面:
src/pages/astronauts/[page].astro
---
export function getStaticPaths({ paginate }) {
const astronautPages = [
{ astronaut: "Neil Armstrong" },
{ astronaut: "Buzz Aldrin" },
{ astronaut: "Sally Ride" },
{ astronaut: "John Glenn" },
];
// 根据宇航员数组生成页面,每页2项
return paginate(astronautPages, { pageSize: 2 });
}
// 所有分页数据都在 "page" 参数中传递
const { page } = Astro.props;
---
<!-- 显示当前页面。也可以使用 `Astro.params.page`!-->
<h1>Page {page.currentPage}</h1>
<ul>
<!-- 列出宇航员信息数组 -->
{page.data.map(({ astronaut }) => <li>{astronaut}</li>)}
</ul>将生成以下两个页面,每页 2 项:
/astronauts/1- 第一页显示“Neil Armstrong”和“Buzz Aldrin”/astronauts/2- 第二页显示“Sally Ride”和“John Glenn”
page 属性
当你使用 paginate() 函数时,每个页面将通过 page 传递数据。page 有很多有用的属性,你可以使用它们来构建页面以及页面之间的链接:
interface Page<T = any> {
/** 数组,包含了传递给 paginate() 函数的页面数据片段 */
data: T[];
/** 元数据 */
/** 页面第一项的计数,从 0 开始。 */
start: number;
/** 页面最后一项的计数,从 0 开始 */
end: number;
/** 结果总计数 */
total: number;
/** 当前页码, 从 1 开始 */
currentPage: number;
/** 每个页面的项数(默认为 10) */
size: number;
/** 最后一页的序号 */
lastPage: number;
url: {
/** 当前页面链接 */
current: string;
/** 前一页的链接(如果有) */
prev: string | undefined;
/** 下一页的链接(如果有) */
next: string | undefined;
/** 第一页的链接 (如果当前页面不是第一页) */
first: string | undefined;
/** 最后一页的链接 (如果当前页面不是最后一页) */
last: string | undefined;
};
}以下示例,展示了当前页面相对于其他相关页面的导航链接:
src/pages/astronauts/[page].astro
---
// 将与上一个示例相同的 `{ astronaut }` 对象列表进行分页
export function getStaticPaths({ paginate }) { /* ... */ }
const { page } = Astro.props;
---
<h1>Page {page.currentPage}</h1>
<ul>
{page.data.map(({ astronaut }) => <li>{astronaut}</li>)}
</ul>
{page.url.first ? <a href={page.url.first}>First</a> : null}
{page.url.prev ? <a href={page.url.prev}>Previous</a> : null}
{page.url.next ? <a href={page.url.next}>Next</a> : null}
{page.url.last ? <a href={page.url.last}>Last</a> : null}了解更多关于 page 分页参数 的内容。
嵌套分页
分页的一个更高级的用例是嵌套分页。当分页与其他动态路由参数相结合时,你可以使用嵌套式分页和一些属性或标签来将分页进行分类。
例如,如果你想通过一些标签来分组你的分页的 Markdown 帖子,你可以通过创建一个 /src/pages/[tag]/[page].astro 的页面来使用嵌套分页,该页面将匹配以下链接。
/red/1(tag=red)/red/2(tag=red)/blue/1(tag=blue)/green/1(tag=green)
嵌套分页的工作原理是使用 getStaticPaths() 返回每个分组的 paginate() 结果数组。
在下面的例子中,我们将实现嵌套分页来建立上面列出的URL。
src/pages/[tag]/[page].astro
---
export function getStaticPaths({paginate}) {
const allTags = ["red", "blue", "green"];
const allPosts = Object.values(import.meta.glob("../pages/post/*.md", { eager: true }));
// 每个标签都会返回 `paginate()` 的结果。
// 确保将 `{ params: { tag }}` 传递给 `paginate()`
// 这样 Astro 才知道怎么把这些结果进行分组
return allTags.flatMap((tag) => {
const filteredPosts = allPosts.filter((post) => post.frontmatter.tag === tag);
return paginate(filteredPosts, {
params: { tag },
pageSize: 10
});
});
}
const { page } = Astro.props;
const params = Astro.params;排除页面
你可以通过在文件名前加上下划线(_)来排除 src/pages 中的页面或目录的构建。带有 _ 前缀的文件不会被路由识别,也不会被放入 dist/ 目录。
你可以使用它来暂时禁用页面,并将测试、工具函数和组件放在与其相关页面同一个文件夹中。
在这个例子中,只有 src/pages/index.astro 和 src/pages/projects/project1.md 将被构建为页面路由和 HTML 文件。

端点(Endpoints)
Astro 允许你创建自定义端点,用于提供任意类型的数据。你可以用它来生成图片、公开 RSS 文档,或者将其作为API 路由,为你的网站构建完整的 API。
在静态生成(SSG)的网站中,自定义端点会在构建时被调用,用于生成静态文件。如果你启用了 SSR(服务端渲染)模式,自定义端点则会变成实时服务器端点,在每次请求时被调用。静态端点与 SSR 端点的定义方式类似,但 SSR 端点支持更多特性。
静态文件端点(Static File Endpoints)
要创建一个自定义端点,只需在 /pages 目录中添加一个 .js 或 .ts 文件。
在构建过程中,.js 或 .ts 扩展名会被移除,因此文件名中应包含你想生成的数据类型的扩展名。例如,src/pages/data.json.ts 将会生成一个 /data.json 端点。
端点需要导出一个 GET 函数(可以是异步的 async),该函数会接收一个上下文对象(context object),其属性与 Astro 全局对象类似。
在这里,它返回一个包含 name 和 url 的 Response 对象。Astro 会在构建时调用它,并使用响应体(body)的内容生成对应的文件。
src/pages/builtwith.json.ts
// 输出:/builtwith.json
export function GET({ params, request }) {
return new Response(
JSON.stringify({
name: "Astro",
url: "https://astro.build/",
}),
);
}访问结果:

如果不使用JSON.stringify,就会输出:

从 Astro v3.0 开始,返回的 Response 对象不再需要包含 encoding 属性。例如,生成一个二进制 png 图像:
src/pages/astro-logo.png.ts
export async function GET({ params, request }) {
const response = await fetch(
"https://docs.astro.build/assets/full-logo-light.png",
);
return new Response(await response.arrayBuffer());
}你还可以使用 APIRoute 类型来约束 API 端点函数:
import type { APIRoute } from "astro";
export const GET: APIRoute = async ({ params, request }) => {...}params 和动态路由
端点支持与页面相同的 动态路由 功能。使用中括号包裹参数作为文件名,并导出 getStaticPaths() 函数。然后,你可以使用传递给 API 端点函数的 params 属性访问参数:
src/pages/api/[id].json.ts
import type { APIRoute } from "astro";
const usernames = ["张三", "李四", "王五", "赵六"];
export const GET: APIRoute = ({ params, request }) => {
const id = params.id;
return new Response(
JSON.stringify({
name: usernames[id],
}),
);
};
export function getStaticPaths() {
return [
{ params: { id: "0" } },
{ params: { id: "1" } },
{ params: { id: "2" } },
{ params: { id: "3" } },
];
}这将在构建时生成四个 JSON 端点:/api/0.json、/api/1.json、/api/2.json 和 /api/3.json。带端点的动态路由与页面的工作方式相同,但由于端点是一个函数而不是组件,因此 props 是不被支持的。
request
所有端点都会接收 request 属性,但在静态模式下,你只能访问 request.url。它将返回当前端点的完整 URL,其工作方式与 Astro.request.url 对页面的作用相同。
src/pages/request-path.json.ts
import type { APIRoute } from "astro";
export const GET: APIRoute = ({ params, request }) => {
return new Response(
JSON.stringify({
path: new URL(request.url).pathname,
}),
);
};服务器端点(API 路由)
静态文件端点部分中描述的所有内容,同样适用于 SSR 模式:文件可以导出一个 GET 函数,该函数会接收一个上下文对象(context object),其属性与全局 Astro 对象类似。
但与静态模式不同的是,当你为某个路由启用按需渲染时,该端点会在被请求时才构建。这解锁了构建时无法使用的新特性,使你能够创建在运行时监听请求、并在服务器上安全执行代码的 API 路由。
在 服务器模式(server mode) 下,路由默认会按需渲染。 而在 静态模式(static mode) 下,你需要为每个自定义端点显式关闭预渲染功能:
export const prerender = false;注意
在尝试这些示例之前,请务必确认已经 启用了按需渲染模式 并且避免了 static 模式下的预渲染。
服务器端点可以在不导出 getStaticPaths 的情况下访问 params。服务器端点可以返回一个 Response 对象,其允许你为返回的数据设置状态码和标头:
src/pages/[id].json.js
import { getProduct } from "../db";
export async function GET({ params }) {
const id = params.id;
const product = await getProduct(id);
if (!product) {
return new Response(null, {
status: 404,
statusText: "Not found",
});
}
return new Response(JSON.stringify(product), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
}这将响应与动态路由匹配的任何请求。例如,如果我们导航到 /helmet.json,params.id 将被设置为 helmet。如果我们模拟的数据库中存在 helmet,端点将会使用 Response 对象(JSON 格式)来响应,并返回成功的 HTTP 状态码。如果没有,它将使用一个 Response 对象返回 404 响应。
在 SSR 模式下,需要提供 Content-Type 头来返回图像。在这种情况下,使用 Response 对象来指定 headers 属性。例如,生成一个二进制 .png 图像:
src/pages/astro-logo.png.ts
export async function GET({ params, request }) {
const response = await fetch(
"https://docs.astro.build/assets/full-logo-light.png",
);
const buffer = Buffer.from(await response.arrayBuffer());
return new Response(buffer, {
headers: { "Content-Type": "image/png" },
});
}HTTP 方法
除了 GET 函数,你还可以使用任何 HTTP 方法 作为名称导出函数。当有请求进来时,Astro 会检查该方法并调用相应的函数。
你还可以导出 ALL 函数以匹配任何没有相应导出函数的方法。如果请求方法不匹配,它将重定向到你的网站的 404 页面。
src/pages/methods.json.ts
export const GET: APIRoute = ({ params, request }) => {
return new Response(
JSON.stringify({
message: "这是个 GET 请求!",
}),
);
};
export const POST: APIRoute = ({ request }) => {
return new Response(
JSON.stringify({
message: "这是个 POST 请求!",
}),
);
};
export const DELETE: APIRoute = ({ request }) => {
return new Response(
JSON.stringify({
message: "这是个 DELETE 请求!",
}),
);
};
export const ALL: APIRoute = ({ request }) => {
return new Response(
JSON.stringify({
message: `这是个 ${request.method} 请求!`,
}),
);
};如果你定义了一个 GET 函数,但没有定义 HEAD 函数,Astro 会自动处理 HEAD 请求:它会调用你的 GET 函数,然后从响应中移除响应体(body)。
HEAD请求是一种 HTTP 请求方法,和GET请求非常相似,但有一个关键区别:
HEAD请求只会请求响应的“头部信息(headers)”,不会返回正文内容(body)。
HEAD通常用于:
- 检查资源是否存在(不下载整个内容)
- 比如判断一个文件、页面或图片是否有效。
- 获取元信息(如文件大小、最后修改时间、内容类型等)
- 适合做缓存验证或下载前的检查。
- 测试接口连通性
- 比如探测一个 API 是否在线。
相关操作指南
request
在 SSR 模式下,request 属性返回一个可用的 Request 对象,该对象引用当前请求。这允许你接收数据并检查 headers:
src/pages/test-post.json.ts
export const POST: APIRoute = async ({ request }) => {
if (request.headers.get("Content-Type") === "application/json") {
const body = await request.json();
const name = body.name;
return new Response(
JSON.stringify({
message: "Your name was: " + name,
}),
{
status: 200,
},
);
}
return new Response(null, { status: 400 });
};重定向
端点上下文导出一个类似于 Astro.redirect 的 redirect() 函数:
src/pages/links/[id].js
import { getLinkUrl } from "../db";
export async function GET({ params }) {
const { id } = params;
const link = await getLinkUrl(id);
if (!link) {
return new Response(null, {
status: 404,
statusText: "Not found"
});
}
return Response.redirect(link, 307);
}中间件
中间件允许你拦截请求和响应,并在即将渲染页面或端点时动态注入行为。对于所有预渲染的页面,这种渲染发生在构建时,但对于按需渲染的页面,这种渲染发生在请求路由时,这时可以使用 额外的 SSR 功能如 cookie 和标头。
中间件(Middleware)还允许你通过修改一个名为 locals 的对象,在所有端点和页面之间设置并共享与当前请求相关的信息。该对象在所有 Astro 组件和 API 端点中都可用,即使中间件在构建时运行,它也同样可被访问。
基本用法
-
创建
src/middleware.js|ts(或者,你也可以创建src/middleware/index.js|ts) -
在这个文件中,导出一个接收
context对象的onRequest()函数。注意这里不能是默认导出。
src/middleware.js
export function onRequest (context, next) {
// 拦截一个请求里的数据
// 可选地修改 `locals` 中的属性
context.locals.title = "New title";
// 返回一个 Response 或者调用 `next()` 的结果
return next();
};- 在任何
.astro文件中,使用Astro.locals访问响应数据。
src/components/Component.astro
---
const data = Astro.locals;
---
<h1>{data.title}</h1>
<p>这个 {data.property} 来自中间件。</p>context 对象
context 对象包含需要对其他中间件、API 路由和 .astro 路由在渲染过程中可用的信息。
它是一个传入 onRequest() 的可选参数,可能包含 locals 对象以及其他在渲染期间共享的任何属性。例如,context 对象可能包含用于认证的 cookies。
在 context.locals 中存储数据
context.locals 是一个可以在中间件中操作、包含来自 Response 的数据的对象。
这个 locals 对象在请求处理过程中被传递,并作为 APIContext 和 AstroGlobal 的属性可用。这使得中间件,API 路由和 .astro 页面之间可以共享数据。这对于在渲染步骤中存储请求特定的数据(例如用户数据)很有用。
集成属性
集成可能会在
locals对象中设置属性和提供一些功能。如果你在使用一个集成,检查它的文档并确认你没有覆盖掉它的属性或者在做重复的工作。
你可以在 locals 中存储任何类型的数据:字符串,数字,甚至复杂的数据类型,如函数和映射。
src/middleware.js
export function onRequest (context, next) {
// 拦截一个请求里的数据
// 可选地修改 `locals` 中的属性
context.locals.user.name = "John Wick";
context.locals.welcomeTitle = () => {
return "Welcome back " + locals.user.name;
};
// 返回一个 Response 或者调用 `next()` 的结果
return next();
};然后你可以在任何 .astro 文件中通过 Astro.locals 使用这个信息。
src/pages/orders.astro
---
const title = Astro.locals.welcomeTitle();
const orders = Array.from(Astro.locals.orders.entries());
const data = Astro.locals;
---
<h1>{title}</h1>
<p>这个 {data.property} 来自中间件。</p>
<ul>
{orders.map(order => {
return <li>{/* do something with each order */}</li>;
})}
</ul>locals 是一个在单个 Astro 路由中创建和销毁的对象;当你的路由页面渲染完后,locals 将不再存在,并会创建一个新的。需要在多个页面请求之间保持的信息必须存储在其他地方。
备注
locals 的值不能在运行时被覆盖。这样做会有清除用户存储的所有信息的风险。Astro 会进行检查,如果 locals 被覆盖将会抛出错误。
示例:删除敏感信息
下面的示例使用中间件将”私人信息”替换为”已删除”,以便你在页面上渲染修改后的 HTML:
src/middleware.js
export const onRequest = async (context, next) => {
const response = await next();
const html = await response.text();
const redactedHtml = html.replaceAll("私人信息", "已删除");
return new Response(redactedHtml, {
status: 200,
headers: response.headers
});
};中间件类型
你可以导入并使用 defineMiddleware() 实用函数来提供类型安全:
src/middleware.ts
import { defineMiddleware } from "astro:middleware";
// `context` 和 `next` 会自动被类型化
export const onRequest = defineMiddleware((context, next) => {
});或者,如果你使用 JsDoc 来提供类型安全,你可以使用 MiddlewareHandler:
src/middleware.js
/**
* @type {import("astro").MiddlewareHandler}
*/
// `context` 和 `next` 会自动被类型化
export const onRequest = (context, next) => {
};要给 Astro.locals 内的信息定义类型,也就是在 .astro 文件和中间件代码中能提供自动补全,通过在 env.d.ts 文件中声明一个全局命名空间来 扩展全局类型:
src/env.d.ts
type User = {
id: number;
name: string;
};
declare namespace App {
interface Locals {
user: User;
welcomeTitle: () => string;
orders: Map<string, object>;
session: import("./lib/server/session").Session | null;
}
}然后,在中间件中,你就可以使用自动补全和类型安全了。
中间件链式调用
可以使用 sequence() 按指定顺序连接多个中间件:
src/middleware.js
import { sequence } from "astro:middleware";
async function validation(_, next) {
console.log("验证请求");
const response = await next();
console.log("验证响应");
return response;
}
async function auth(_, next) {
console.log("授权请求");
const response = await next();
console.log("授权响应");
return response;
}
async function greeting(_, next) {
console.log("问候请求");
const response = await next();
console.log("问候响应");
return response;
}
export const onRequest = sequence(validation, auth, greeting);控制台打印结果顺序如下:

重写
添加于: astro@4.13.0
APIContext 中暴露了一个名为 rewrite() 的方法,它的工作方式与 Astro.rewrite 相同。
使用 context.rewrite() 在中间件中显示不同页面的内容,而不会将访问者 重定向 到新页面。这将触发一个新的渲染阶段,导致任何中间件被重新执行。
src/middleware.js
import { isLoggedIn } from "~/auth.js"
export function onRequest (context, next) {
if (!isLoggedIn(context)) {
// 如果用户未登录,则更新请求来渲染 `/login` 路由,
// 并添加标头以指示在成功登录应该把用户重定向到何处。
// 重新执行中间件。
return context.rewrite(new Request("/login", {
headers: {
"x-redirect-to": context.url.pathname
}
}));
}
return next();
};你也可以向 next() 函数传递一个可选的 URL 路径参数,以重写当前的 Request 而不重新触发新的渲染阶段(绕过中间件)。重写路径的位置可以作为字符串、URL 或 Request 提供:
src/middleware.js
import { isLoggedIn } from "~/auth.js"
export function onRequest (context, next) {
if (!isLoggedIn(context)) {
// 如果用户未登录,则更新请求来渲染 `/login` 路由,
// 并添加标头以指示在成功登录应该把用户重定向到何处。
// 将新的 `context` 返回给任何后续中间件。
return next(new Request("/login", {
headers: {
"x-redirect-to": context.url.pathname
}
}));
}
return next();
};next() 函数接受与 Astro.rewrite() 相同的参数。重写路径的位置可以通过字符串、URL 或 Request 提供。
当你使用 sequence() 将多个中间件函数串联时,如果在 next() 中传入了一个路径参数,请求将会被就地重写(rewrite),并且中间件链不会重新执行。下一个中间件函数将接收到这个带有更新上下文的新请求(Request)。
使用这种方式调用 next() 时,会基于旧的 ctx.request 创建一个新的 Request 对象。这意味着如果你在重写前或重写后尝试读取 Request.body,会抛出运行时错误。
这种错误常见于使用 HTML 表单的 Astro Actions 中。在这些情况下,建议在 Astro 模板中使用 Astro.rewrite() 来处理重写逻辑,而不要在中间件中使用。
src/middleware.js
// 当前 URL 是 https://example.com/blog
// 第一个中间件函数
async function first(context, next) {
console.log(context.url.pathname) // 这里会打印 "/blog"
// 重写到一个新的路由,首页
// 返回更新的 `context`,传递给下一个函数
return next("/")
}
// 当前 URL 仍然是 https://example.com/blog
// 第二个中间件函数
async function second(context, next) {
// 接收更新后的 `context`
console.log(context.url.pathname) // 这里会打印 "/"
return next()
}
export const onRequest = sequence(first, second);错误页面
即使找不到匹配的路由,中间件也会尝试为所有按需渲染的页面运行。这包括 Astro 的默认(空白)404 页面和任何自定义 404 页面。然而,是否运行该代码取决于适配器。一些适配器可能会提供特定平台的错误页面。
在提供 500 错误页面之前,中间件也会尝试运行,包括自定义的 500 页面,除非服务器错误发生在中间件本身的执行中。如果你的中间件没有成功运行,那么你将无法访问 Astro.locals 来渲染你的 500 页面。
APIAPI
https://docs.astro.build/zh-cn/reference/api-reference/#redirect
评论
评论加载中……