路由

Astro 的路由基于文件,它根据项目的 src/pages 目录中的文件结构来生成你的构建链接。当一个文件被添加到 src/pages 目录中,它将自动基于文件名生成与之对应的路由。

src/pages 目录中的 Astro 组件(.astro)和 Markdown 文件(.md)将自动成为网站页面。每个页面的路由都和其在 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/1

一个单独的 Astro 页面组件也可以在其文件名中指定动态路由参数,以生成符合给定条件的多个路由。你可以一次创建几个相关的页面,如作者页面,或为每个博客标签创建一个页面。命名参数允许你为这些路由设置给定值,而其余参数允许生成更灵活的全能路由。

Astro 页面创建动态路由必须:

  1. 使用 [bracket] 标记来识别动态参数

  2. 导出 getStaticPaths() 函数来明确要由 Astro 进行预渲染的路径。

你可以通过向 getStaticPaths() 函数提供要使用的值来生成带有 [named] 参数的路由,如:

src/pages/dogs/[dog].astro
---
export function getStaticPaths() {
  return [
    // 生成:/dogs/clifford
    {params: {dog: 'clifford'}},
    // 生成:/dogs/rover
    {params: {dog: 'rover'}},
    // 生成:/dogs/spot
    {params: {dog: 'spot'}},
  ];
}
---

📚 了解更多关于 getStaticPaths() 的信息。

路由可以根据在文件路径任一层级的多个参数生成:

  • pages/blog/[slug].astro → (/blog/hello-world, /blog/post-2, etc.)
  • pages/[username]/settings.astro → (/fred/settings, /drew/settings, etc.)
  • pages/[lang]-[version]/info.astro → (/en-v1/info, /fr-v2/info, etc.)

动态生成路由的 Astro 组件可以访问每个路由的 Astro.params 对象。这使得你可以在组件脚本和模板中使用那些生成的链接部分。

src/pages/posts/[id].astro
---
const { id } = Astro.params;
---
<p>Post: { id }</p>


// Astro.params 对象将传递给 `/post/abc` 路由
{ "id": "abc" }

多个动态路由段可以结合起来以同样的方式工作。

src/pages/post/[id]/[comment].astro
---
const { id, comment } = Astro.params;
---

// Astro.params 对象将传递给 `/post/abc/a-comment` 路由
{ "id": "abc", "comment": "a-comment" }

如果你的链接路由需要更加灵活,你可以在 .astro 文件名中使用一个其他参数,作为任何深度的文件路径的通用集合,在括号内添加三个点(...)。

例如:

  • pages/post/[...slug].astro → (/post/a, /post/a/b, /post/a/b/c, etc.)

匹配的参数将作为查询参数(例子中的 slug)传递给页面。

// Astro.params 将传递给 `/post/a/b/c` 路由
{ "slug": "a/b/c" }

一个真实示例,你可以用以下命名参数和其他参数来实现 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'
}

一个链接可能匹配多个路由。例如下面的每个路由都会匹配 /posts/create

└── pages/
│       ├── posts/
│       │   ├── create.astro
│       │   ├── [pid].astro
│       │   └── [...slug].astro

Astro 需要知道哪个路由应该被用来建立页面。为了做到这一点,它根据以下规则对它们进行排序。

  • 没有路径参数的静态路由将优先于所有其他路由
  • 使用命名参数的动态路由优先于其余参数
  • 其他参数的优先级最低
  • 平局则按字母顺序解决

鉴于上面的例子,下面是几个例子,说明规则如何将请求的URL与用于建立HTML的路由相匹配。

  • pages/posts/create.astro - 将生成 /posts/create
  • pages/posts/[pid].astro - 将生成 /posts/1/posts/abc 等路由。但不包括 /posts/create
  • pages/posts/[...slug].astro - 将生成 /posts/1/2/posts/a/b/c 等路由. 但不包括 /posts/create/posts/1/posts/abc

Astro 支持内置分页,用于需要分割成多个页面的大型数据集合。Astro 会生成常见的分页属性,包括上一页/下一页链接、总页数,以及其他信息。

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

你可以用 paginate() 函数根据数组值生成这些页面,像这样。

/src/pages/astronauts/[page].astro
---
export async function getStaticPaths({ paginate }) {
  const astronautPages = [{
    astronaut: 'Neil Armstrong',
  }, {
    astronaut: 'Buzz Aldrin',
  }, {
    astronaut: 'Sally Ride',
  }, {
    astronaut: 'John Glenn',
  }];
  // 将根据宇航员数组生成两个页面
  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>

将生成以下两个页面:

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

当你使用 paginate() 函数时,每个页面将通过 page 参数传递数据。page 参数有很多有用的属性,下面列出最为重要的:

  • page.data - 数组,包含你传递给 paginate() 函数的页面的数据片段
  • page.url.next - 链接到该集合中的下一个页面
  • page.url.prev - 链接到集合中的上一个页面
/src/pages/astronauts/[page].astro
---
// 将与上一个示例相同的 { astronaut } 对象进行分页
export async function getStaticPaths({ paginate }) { /* ... */ }
const { page } = Astro.props;
---
<h1>Page {page.currentPage}</h1>
<ul>
  {page.data.map(({ astronaut }) => <li>{astronaut}</li>)}
</ul>
{page.url.prev ? <a href={page.url.prev}>Previous</a> : null}
{page.url.next ? <a href={page.url.next}>Next</a> : null}
interface Page<T = any> {
  /** 结果 */
  data: T[];
  /** 元数据 */
  /** 第一个页面的序号,从 0 开始。 */
  start: number;
  /** 最后一个页面的序号,从 0 开始 */
  end: number;
  /** 结果总计数量 */
  total: number;
  /** 当前页面序号, 从 1 开始 */
  currentPage: number;
  /** 每个页面的项目数量(默认为 25) */
  size: number;
  /** 最后一页的序号 */
  lastPage: number;
  url: {
    /** 当前页面链接 */
    current: string;
    /** 前一页的链接(如果有) */
    prev: string | undefined;
    /** 下一页的链接(如果有) */
    next: string | undefined;
  };
}

分页的一个更高级的用例是嵌套分页。当分页与其他动态路由参数相结合时,你可以使用嵌套式分页和一些属性或标签来将分页进行分类。

例如,如果你想通过一些标签来分组你的分页的 Markdown 帖子,你可以通过创建一个 /src/pages/[tag]/[page].astro 的页面来使用嵌套分页,该页面将匹配以下链接。

  • /red/1 (tag=red)
  • /red/2 (tag=red)
  • /blue/1 (tag=blue)
  • /green/1 (tag=green)

嵌套分页的工作方式是使用 getStaticPaths() 并返回每个分组的 paginate() 结果的数组。

在下面的例子中,我们将实现嵌套分页来建立上面列出的链接。

/src/pages/[tag]/[page].astro
---
export async function getStaticPaths({paginate}) {
  const allTags = ['red', 'blue', 'green'];
  const allPosts = await Astro.glob('../../posts/*.md');
  // 每个标签都会返回 paginate() 结果。
  // 确保将 `{params: {tag}}` 传递给 `paginate()`
  // 这样 Astro 才知道怎么把这些结果进行分组
  return allTags.map((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;