import { FileTree } from '@astrojs/starlight/components'; import RecipeLinks from "/components/RecipeLinks.astro" import Since from '/components/Since.astro' import ReadMore from '~/components/ReadMore.astro'

Astro 使用基于文件的路由,它根据项目 src/pages 目录中的文件结构来生成你的构建链接。

页面之间的导航

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

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/ 目录中的路径和文件名。

diff
# 示例:静态路由src/pages/index.astro        -> mysite.com/src/pages/about.astro        -> mysite.com/aboutsrc/pages/about/index.astro  -> mysite.com/aboutsrc/pages/about/me.astro     -> mysite.com/about/mesrc/pages/posts/1.md         -> mysite.com/posts/1

:::tip Astro 项目没有单独的路由配置!当你在 src/pages 目录新增文件时,会自动生成一个新的路由。在静态生成中,你可以使用 build.format 配置项自定义文件输出格式。 :::

动态路由

Astro 页面文件可以在其文件名中指定动态路由参数以生成多个匹配页面。

例如,src/pages/authors/[author].astro 可以为你博客上的每一位作者生成一个简介页面,而 author 将作为一个你可以从页面内部访问的 参数

在 Astro 默认的静态输出模式中,这些页面是在构建时生成的,因此你必须预设对应文件的 author 列表。而在 SSR 模式下,将根据请求为任何匹配的路由生成一个页面。

静态 (SSG) 模式

因为必须在构建时确定所有路由,所以动态路由必须导出一个 getStaticPaths() ,它返回一个具有 params 属性的对象数组。数组中的每一个对象都会生成相应的路由。

[dog].astro 在其文件名中定义一个 dog 参数,则 getStaticPaths() 返回的这些对象的 params 中必须包含 dog 。 然后页面可以使用 Astro.params 访问该参数。

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 对象中:

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 函数。

astro
--- export function getStaticPaths() {  return [    { params: { slug: decodeURI("%5Bpage%5D") }}, // 解码成 "[page]"  ]}---

了解更多关于 getStaticPaths() 的内容.

<RecipeLinks slugs={["zh-cn/recipes/i18n"]} />

剩余参数

如果你的URL路由需要更加灵活,你可以在 .astro 文件名中使用一个 剩余参数[...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 的请求将拆分为以下命名参数:

js
{	org: "withastro",	repo: "astro",	branch: "main",	file: "docs/public/favicon.svg"}

示例:多级动态页面

在下面的示例中, 剩余参数([...slug])和 getStaticPaths()props 为不同深度的 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].astrosrc/pages/[...locale]/[slug].astro,而不是 src/pages/[...locale]/[...slug].astro

astro
---export const prerender = false; // 'server' 模式下,无需配置const { resource, id } = Astro.params;---<h1>{resource}: {id}</h1>

该页面将为任意的 resourceid 提供服务: resources/users/1resources/colors/blue 等。

修改 [...slug] 示例用于SSR

由于 SSR 页面无法使用 getStaticPaths(),因此无法接收 props。可以通过在对象中查找 slug 参数的值,将 前面的示例 调整为 SSR 模式。如果路由位于根目录(“/”),则 slug 参数为 undefined。如果对象中不存在该值,将重定向到 404 页面。

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 配置中,你可以使用 redirects 值来指定永久重定向的映射关系。

对于内部重定向,这是旧路由路径到新路由的映射。从 Astro v5.2.0 开始,还可以重定向到以 httphttps 开头的外部 URL,并且 可以被解析

js
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

只要新旧路由都包含相同的参数,就可以使用动态路由,例如:

js
{  "/blog/[...slug]": "/articles/[...slug]"}

使用 SSR(服务器端渲染)或者静态适配器,你还可以将对象作为值提供,除了新的 destination 端点指向之外,还可以指定 status 状态码:

js
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 中获取其会话)之后执行此操作。

astro
---import { isLoggedIn } from "../utils";const cookie = Astro.request.headers.get("cookie");// 如果用户未登录,将其重定向到登录页面。if (!isLoggedIn(cookie)) {  return Astro.redirect("/login");}---

重写

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

:::tip 对于永久性移动的内容,或者将用户重定向到具有新 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 生成的内容。

astro
---return Astro.rewrite("/es/articles/introduction");---

使用 context.rewrite() 在你的端点文件中重定向到不同的页面:

js
export function GET(context) {  if (!context.locals.allowed) {    return context.rewrite("/");  }}

如果传递给 Astro.rewrite() 的 URL 引发运行时错误,Astro 将在开发中显示错误覆盖层,并在生产中返回 500 状态码。如果你的项目中不存在该 URL,则将返回 404 状态码。

你可以有意创建一个重写来渲染你的 /404 页面,例如,以指示你的电子商店中的产品不再可用:

astro
---const { item } = Astro.params;if (!itemExists(item)) {  return Astro.rewrite("/404");}---

你也可以根据 HTTP 响应状态有条件地重写,例如在访问不存在的 URL 时显示站点上的某个页面:

js
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/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/: 服务于任何已定义的 action

分页

Astro 支持内置分页,用于需要分割成多个页面的大量数据。Astro 会生成常见的分页属性,包括上一页/下一页链接、总页数等。

分页的路由名应该使用与标准动态路由一样的 [bracket] 语法。例如,文件名 /astronauts/[page].astro 将生成 /astronauts/1/astronauts/2 等路由,其中 [page] 是生成的页码。

你可以用 paginate() 函数根据数组值生成这些页面:

astro
---// src/pages/astronauts/[page].astroexport 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 有很多有用的属性,你可以使用它们来构建页面以及页面之间的链接:

ts
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;	};}

以下示例,展示了当前页面相对于其他相关页面的导航链接:

astro
---// 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。

astro
---// src/pages/[tag]/[page].astroexport 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.astrosrc/pages/projects/project1.md 将被构建为页面路由和 HTML 文件。