跳转到内容

路由

Astro 使用基于文件的路由,根据项目 src/pages/ 目录的文件布局生成你的构建 URL。

Astro 使用标准的 HTML <a> 元素在路由之间导航。没有提供特定于框架的 <Link> 组件。

src/pages/index.astro
<p>Read more <a href="/about/">about</a> Astro!</p>
<!-- With `base: "/docs"` configured -->
<p>Learn more in our <a href="/docs/reference/">reference</a> section!</p>

src/pages/ 目录中的 .astro 页面组件以及 Markdown 和 MDX 文件(.md.mdx会自动成为你网站上的页面。每个页面的路由都对应其在 src/pages/ 目录中的路径和文件名。

# Example: Static routes
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/1

Astro 页面文件可以在其文件名中指定动态路由参数,以生成多个匹配的页面。例如,src/pages/authors/[author].astro 会为博客中的每个作者生成一个简介页面。author 成为你可以在页面内部访问的参数

在 Astro 默认的静态输出模式下,这些页面在构建时生成,因此你必须预先确定获得相应文件的 author 列表。在 SSR 模式下,将根据请求为任何匹配的路由生成页面。

由于所有路由都必须在构建时确定,因此动态路由必须导出一个 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

提供给 getStaticPaths() 函数的 params 未被解码。当需要解码参数值时,请使用 decodeURI 函数。

src/pages/[slug].astro
---
export function getStaticPaths() {
return [
{ params: { slug: decodeURI("%5Bpage%5D") }}, // decodes to "[page]"
]
}
---
了解更多关于 getStaticPaths()
相关指南: 添加 i18n 功能

如果你在 URL 路由中需要更大的灵活性,可以在你的 .astro 文件名中使用 rest 参数 ([...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。(将 rest 参数设置为 undefined 允许它匹配顶层页面。)

Rest 参数可以与其他命名参数一起使用。例如,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"
}

在以下示例中,一个 rest 参数 ([...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

对于按需渲染的路由,文件名中只能使用一个使用扩展语法的 rest 参数(例如 src/pages/[locale]/[...slug].astrosrc/pages/[...locale]/[slug].astro,但不能是 src/pages/[...locale]/[...slug].astro)。

src/pages/resources/[resource]/[id].astro
---
export const prerender = false; // Not needed in 'server' mode
const { resource, id } = Astro.params;
---
<h1>{resource}: {id}</h1>

此页面将针对 resourceid 的任何值提供服务:resources/users/1resources/colors/blue 等。

由于 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 开始,还可以重定向到以 httphttps 开头且可被解析的外部 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 the user is not logged in, redirect them to the login page
if (!isLoggedIn(cookie)) {
return Astro.redirect("/login");
}
---

新增于: astro@4.13.0

重写允许你提供一个不同的路由,而无需将浏览器重定向到不同的页面。浏览器将在 URL 栏中显示原始地址,但会显示提供给 Astro.rewrite() 的 URL 的内容。

重写对于在多个路径上显示相同内容非常有用(例如 /products/shoes/men//products/men/shoes/),而无需维护两个不同的源文件。

重写对于 SEO 和用户体验也很有用。它们允许你显示那些否则需要将访问者重定向到不同页面或会返回 404 状态的内容。重写的一个常见用途是为一种语言的不同变体显示相同的本地化内容。

以下示例使用重写,在访问 /es-CU/(古巴西班牙语)URL 路径时,渲染页面的 /es/ 版本。当访问者导航到 URL /es-cu/articles/introduction 时,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

  • 目录src/pages/
    • […slug].astro
    • 目录posts/
      • create.astro
      • [page].astro
      • [pid].ts
      • […slug].astro

Astro 需要知道应该使用哪个路由来构建页面。为此,它按顺序根据以下规则对它们进行排序:

  • Astro 保留路由
  • 具有更多路径段的路由将优先于不太具体的路由。在上面的例子中,/posts/ 下的所有路由都优先于根目录下的 /[...slug].astro
  • 没有路径参数的静态路由将优先于动态路由。例如,/posts/create.astro 优先于示例中的所有其他路由。
  • 使用命名参数的动态路由优先于 rest 参数。例如,/posts/[page].astro 优先于 /posts/[...slug].astro
  • 预渲染的动态路由优先于服务器动态路由。
  • 端点优先于页面。
  • 基于文件的路由优先于重定向。
  • 如果以上规则都不能决定顺序,则路由将根据你的 Node 安装的默认区域设置按字母顺序排序。

鉴于上面的例子,以下是一些关于规则如何将请求的 URL 匹配到用于构建 HTML 的路由的示例:

  • pages/posts/create.astro - 只会构建 /posts/create
  • pages/posts/[pid].ts - 会构建 /posts/abc/posts/xyz 等。但不会构建 /posts/create
  • pages/posts/[page].astro - 会构建 /posts/1/posts/2 等。但不会构建 /posts/create/posts/abc/posts/xyz
  • pages/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/:提供任何已定义的操作

Astro 支持内置的分页功能,用于需要拆分为多个页面的大型数据集合。Astro 将生成常见的分页属性,包括上一页/下一页 URL、总页数等。

分页路由名称应使用与标准动态路由相同的 [括号] 语法。例如,文件名 /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" },
];
// Generate pages from our array of astronauts, with 2 to a page
return paginate(astronautPages, { pageSize: 2 });
}
// All paginated data is passed on the "page" prop
const { page } = Astro.props;
---
<!-- Display the current page number. `Astro.params.page` can also be used! -->
<h1>Page {page.currentPage}</h1>
<ul>
<!-- List the array of astronaut info -->
{page.data.map(({ astronaut }) => <li>{astronaut}</li>)}
</ul>

这将生成以下页面,每页 2 项:

  • /astronauts/1 - 第 1 页:显示“Neil Armstrong”和“Buzz Aldrin”
  • /astronauts/2 - 第 2 页:显示“Sally Ride”和“John Glenn”

当你使用 paginate() 函数时,每个页面都会通过 page 属性接收其数据。page 属性有许多有用的属性,可用于构建页面和它们之间的链接:

interface Page<T = any> {
/** array containing the page’s slice of data that you passed to the paginate() function */
data: T[];
/** metadata */
/** the count of the first item on the page, starting from 0 */
start: number;
/** the count of the last item on the page, starting from 0 */
end: number;
/** total number of results */
total: number;
/** the current page number, starting from 1 */
currentPage: number;
/** number of items per page (default: 10) */
size: number;
/** number of last page */
lastPage: number;
url: {
/** url of the current page */
current: string;
/** url of the previous page (if there is one) */
prev: string | undefined;
/** url of the next page (if there is one) */
next: string | undefined;
/** url of the first page (if the current page is not the first page) */
first: string | undefined;
/** url of the last page (if the current page in not the last page) */
last: string | undefined;
};
}

以下示例显示了页面的当前信息以及在页面之间导航的链接:

src/pages/astronauts/[page].astro
---
// Paginate same list of `{ astronaut }` objects as the previous example
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 页面来使用嵌套分页,该页面将匹配以下 URL:

  • /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 }));
// For every tag, return a `paginate()` result.
// Make sure that you pass `{ params: { tag }}` to `paginate()`
// so that Astro knows which tag grouping the result is for.
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.astrosrc/pages/projects/project1.md 会被构建为页面路由和 HTML 文件。

  • 目录src/pages/
    • 目录_hidden-directory/
      • page1.md
      • page2.md
    • _hidden-page.astro
    • index.astro
    • 目录projects/
      • _SomeComponent.astro
      • _utils.js
      • project1.md
贡献 社区 赞助