getStaticPaths():静态路由生成
动态路由的基本概念
在 Astro 中,文件名中带 [参数] 的页面是动态路由。对于静态生成(SSG)模式,需要通过 getStaticPaths() 函数告诉 Astro 需要生成哪些具体的页面。
---
// src/pages/blog/[slug].astro
import { getCollection } from 'astro:content';
// getStaticPaths 在构建时执行,返回所有可能的路由参数
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug }, // URL 参数
props: { post }, // 传给页面的 Props
}));
}
// 从 Props 获取当前页面数据
const { post } = Astro.props;
const { Content } = await post.render();
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>
REST 参数:匹配多级路径
---
// src/pages/docs/[...slug].astro
// 匹配 /docs/getting-started、/docs/api/users、/docs/a/b/c
export async function getStaticPaths() {
return [
{ params: { slug: 'getting-started' } }, // /docs/getting-started
{ params: { slug: 'api/users' } }, // /docs/api/users
{ params: { slug: undefined } }, // /docs/ (REST 参数可以为空)
];
}
// Astro.params.slug 是字符串或 undefined
const { slug } = Astro.params;
---
paginate():内置分页
---
// src/pages/blog/[page].astro
import { getCollection } from 'astro:content';
export async function getStaticPaths({ paginate }) {
const posts = await getCollection('blog');
// 按日期排序
posts.sort((a, b) => b.data.pubDate.getTime() - a.data.pubDate.getTime());
// paginate() 自动生成分页路由
return paginate(posts, {
pageSize: 10, // 每页 10 篇
});
}
// page 对象包含分页信息
const { page } = Astro.props;
// page.data: 当前页的文章数组
// page.currentPage: 当前页码(1-indexed)
// page.total: 总条数
// page.totalPages: 总页数
// page.url.prev: 上一页 URL
// page.url.next: 下一页 URL
---
<ul>
{page.data.map(post => (
<li><a href={`/blog/${post.slug}`}>{post.data.title}</a></li>
))}
</ul>
<nav>
{page.url.prev && <a href={page.url.prev}>上一页</a>}
<span>{page.currentPage} / {page.totalPages}</span>
{page.url.next && <a href={page.url.next}>下一页</a>}
</nav>
按需渲染(SSR)vs 静态预渲染
export const prerender = true
(混合模式默认值)页面在构建时生成为静态 HTML,不需要服务器动态处理。适合内容不频繁变化的页面。
export const prerender = false
页面在每次请求时服务端动态渲染。适合依赖用户身份、实时数据的页面(如用户个人页、搜索结果)。
构建输出分析
# 运行构建并查看输出统计
npm run build
# 典型静态构建输出(dist/ 目录)
dist/
├── index.html
├── about/
│ └── index.html
├── blog/
│ ├── index.html # /blog
│ ├── first-post/
│ │ └── index.html # /blog/first-post
│ └── ...
└── _astro/
├── page.Bx3kLpQm.css # 哈希文件名(缓存破坏)
└── Counter.B7fK2pXt.js # 岛屿组件的 JS
Astro 的 JS 输出极其精简
Astro 只会为实际使用了 client: 指令的组件输出 JS 文件。如果你的整个网站没有任何交互岛屿,dist/_astro/ 目录中将没有任何 .js 文件——这是真正的零 JS 输出。
getStaticPaths 的工作时机
构建时执行(Build Time)
getStaticPaths 在
npm run build 时执行,不是运行时。它可以调用 API、读取文件系统、查询数据库——只要能在 Node.js 环境运行即可。执行结果决定了最终生成的 HTML 文件数量。params 与 props 的区别
params 是路由参数(决定 URL);props 是传给页面的数据。将数据放在 props 而非 params 的原因:props 可以传递任何类型(对象、数组),不需要序列化为字符串;params 只能是字符串,用于构造 URL。
fallback 处理
静态生成模式下,访问未在 getStaticPaths 中声明的路径会得到 404。如需支持动态新增内容(无需重新构建),需切换到 SSR 模式(output: 'server')。
多参数动态路由
---
// src/pages/[lang]/blog/[slug].astro
// 匹配 /zh/blog/my-post、/en/blog/my-post 等
export async function getStaticPaths() {
const languages = ['zh', 'en', 'ja'];
const posts = await fetch('/api/posts').then(r => r.json());
// 笛卡尔积:所有语言 × 所有文章的组合
return languages.flatMap(lang =>
posts.map(post => ({
params: { lang, slug: post.slug },
props: { post, lang },
}))
);
}
const { post, lang } = Astro.props;
---
<h1 lang={lang}>{post.title}</h1>
View Transitions:SPA 风格的页面切换动画
View Transitions API 让 Astro 的多页面应用(MPA)实现类似单页应用(SPA)的页面切换效果——无需客户端路由框架。原理是:Astro 拦截链接点击,用 fetch 获取下一页的 HTML,然后用浏览器原生的 View Transitions API 在新旧 DOM 之间播放过渡动画,最后替换 DOM。
View Transitions API(浏览器原生)
Chrome 111+ / Safari 18+ 支持的浏览器原生动画 API。通过 document.startViewTransition() 在 DOM 变更前后捕获快照并插值动画。Astro 封装了这个 API,使其在多页面应用中无缝工作。
ViewTransitions 组件
Astro 内置组件,放入 Layout 的 <head> 中即可为整站启用 View Transitions。自动注入客户端路由逻辑,拦截导航、管理 <head> 更新、触发过渡动画。
transition:name
为特定元素指定命名过渡。相同 transition:name 的元素在页面间切换时,浏览器会自动对它们播放位置/大小变换动画(即"共享元素过渡"),产生流畅的视觉连续性效果。
transition:persist
标记此元素在页面切换时保持状态,不被替换(如音乐播放器、视频)。适合需要跨页持续播放的媒体元素,或需要在页面间保留滚动位置的侧边栏。
启用 View Transitions
---
// src/layouts/BaseLayout.astro
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<title>{Astro.props.title}</title>
<!-- 放入 head 中即可为整站启用 View Transitions -->
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>
共享元素过渡(Shared Element Transition)
最常见的用途是列表页 → 详情页的平滑过渡:列表中的图片"飞"到详情页的位置。实现方式是给两个页面的对应元素设置相同的 transition:name:
---
// src/pages/blog/index.astro(列表页)
const posts = await getCollection('blog');
---
{posts.map(post => (
<a href={`/blog/${post.id}/`}>
<!-- transition:name 相同的元素,在页面切换时自动动画 -->
<img
src={post.data.cover}
alt={post.data.title}
transition:name={`post-cover-${post.id}`}
/>
<h2 transition:name={`post-title-${post.id}`}>
{post.data.title}
</h2>
</a>
))}
---
// src/pages/blog/[id].astro(详情页)
const { id } = Astro.params;
const post = await getEntry('blog', id);
---
<article>
<!-- 与列表页相同的 transition:name,浏览器自动播放元素过渡动画 -->
<img
src={post.data.cover}
alt={post.data.title}
transition:name={`post-cover-${post.id}`}
/>
<h1 transition:name={`post-title-${post.id}`}>
{post.data.title}
</h1>
<div set:html={post.body}></div>
</article>
自定义过渡动画
---
import { fade, slide, fly } from 'astro:transitions';
---
<!-- 内置动画:fade(淡入淡出)、slide(滑动)、none(无动画)-->
<main transition:animate={fade()}>内容</main>
<aside transition:animate={slide({ duration: '0.3s' })}>侧栏</aside>
<!-- 自定义 CSS 动画 -->
<header transition:animate="my-header-anim">导航栏</header>
<style>
/* ::view-transition-old(root) 是旧页面消失时的动画 */
@keyframes fade-in {
from { opacity: 0; transform: translateY(8px); }
}
@keyframes fade-out {
to { opacity: 0; transform: translateY(-8px); }
}
::view-transition-old(root) { animation: fade-out 0.15s ease-out; }
::view-transition-new(root) { animation: fade-in 0.2s ease-out; }
</style>
View Transitions 生命周期事件
// 在 script 标签中监听导航生命周期事件
document.addEventListener('astro:before-preparation', (e) => {
// 即将开始加载新页面(可调用 e.cancel() 取消导航)
});
document.addEventListener('astro:after-preparation', () => {
// 新页面 HTML 已获取,即将开始过渡动画
});
document.addEventListener('astro:page-load', () => {
// 新页面加载完成(等同于 DOMContentLoaded,每次导航都触发)
// 重要:初始化客户端库/事件监听器要在这里,不能只在首次加载时初始化
document.querySelectorAll('[data-tooltip]').forEach(initTooltip);
});
View Transitions 的常见陷阱
- script 重复执行:View Transitions 不刷新页面,普通 <script> 在首次加载时执行一次后不再执行。如果需要在每次导航后重新初始化(如第三方 Widget、分析代码),必须监听
astro:page-load事件。 - transition:name 必须全局唯一:同一时刻页面上不能有两个相同 transition:name 的元素,否则浏览器不知道对哪个元素应用过渡。列表页的动态 transition:name 要包含唯一 ID(如文章 id)。
- 浏览器兼容性:View Transitions API 在 Safari 18 之前不支持。Astro 会自动降级——不支持的浏览器会进行普通的全页面刷新,不影响功能,只是没有动画效果。
本章小结
本章核心要点
- getStaticPaths 必须性:静态模式下,任何带 [参数] 的页面都必须导出 getStaticPaths(),否则构建失败——Astro 必须在构建时知道所有需要生成的 URL。
- params vs props:params 决定 URL(只能是字符串);props 传递页面数据(可以是任意 JS 对象)。将数据放在 props 避免了在 URL 参数中携带复杂数据的需要。
- REST 参数 [...slug]:匹配任意深度的路径段;slug 值为 undefined 时匹配根路径(如 /docs/);适合多级文档导航、多语言路径等场景。
- paginate() 内置分页:传入完整数据集 + pageSize,Astro 自动生成 /page/1、/page/2 等路由;page.url.prev/next 用于分页导航链接;page.data 是当前页的数据子集。
- View Transitions API:在 Layout 的 <head> 中加入 <ViewTransitions />,即可为整站启用 SPA 风格页面切换;用 transition:name 为对应元素设置共享元素动画(列表图到详情图的"飞入"效果);用 astro:page-load 事件代替 DOMContentLoaded 初始化脚本。
- 构建输出规律:每个路由生成一个 index.html 文件(如 /blog/post-1/ → dist/blog/post-1/index.html);岛屿 JS 文件带内容哈希指纹用于长期缓存。