页面与路由 #

一、页面基础 #

1.1 什么是 Astro 页面? #

Astro 页面是位于 src/pages/ 目录下的文件,每个文件自动成为一个可访问的路由。

text
src/pages/
├── index.astro        → /
├── about.astro        → /about
├── contact.astro      → /contact
└── blog/
    ├── index.astro    → /blog
    └── post.astro     → /blog/post

1.2 页面文件类型 #

Astro 支持多种页面文件类型:

文件类型 说明
.astro Astro 组件页面
.md Markdown 页面
.mdx MDX 页面(需要集成)
.html 纯 HTML 页面
.js/.ts API 端点

二、静态路由 #

2.1 基本路由 #

text
src/pages/
├── index.astro        → /
├── about.astro        → /about
├── services.astro     → /services
└── contact.astro      → /contact

2.2 嵌套路由 #

text
src/pages/
├── blog/
│   ├── index.astro    → /blog
│   ├── latest.astro   → /blog/latest
│   └── popular.astro  → /blog/popular
├── docs/
│   ├── index.astro    → /docs
│   ├── intro.astro    → /docs/intro
│   └── guide.astro    → /docs/guide
└── products/
    ├── index.astro    → /products
    └── list.astro     → /products/list

2.3 页面示例 #

astro
---
// src/pages/about.astro
import Layout from '../layouts/Layout.astro';
---

<Layout title="关于我们">
  <h1>关于我们</h1>
  <p>这是关于页面的内容。</p>
</Layout>

三、动态路由 #

3.1 基本动态路由 #

使用 [参数名] 创建动态路由:

text
src/pages/
├── blog/
│   └── [slug].astro   → /blog/:slug
└── users/
    └── [id].astro     → /users/:id

3.2 动态路由示例 #

astro
---
// src/pages/blog/[slug].astro
import Layout from '../../layouts/Layout.astro';

// 获取动态参数
const { slug } = Astro.params;

// 根据 slug 获取文章数据
const post = await getPost(slug);

if (!post) {
  return Astro.redirect('/404');
}
---

<Layout title={post.title}>
  <article>
    <h1>{post.title}</h1>
    <p>{post.content}</p>
  </article>
</Layout>

3.3 多个动态参数 #

astro
---
// src/pages/blog/[category]/[slug].astro

const { category, slug } = Astro.params;

// URL: /blog/tech/hello-world
// category = 'tech'
// slug = 'hello-world'
---

<h1>分类: {category}</h1>
<h2>文章: {slug}</h2>

3.4 剩余参数路由 #

使用 [...参数名] 匹配剩余路径:

astro
---
// src/pages/docs/[...path].astro

const { path } = Astro.params;

// URL: /docs/guide/getting-started
// path = 'guide/getting-started'
---

<h1>文档路径: {path}</h1>

四、静态生成(getStaticPaths) #

4.1 基本用法 #

在静态模式下,动态路由需要使用 getStaticPaths 预定义所有路径:

astro
---
// src/pages/blog/[slug].astro

export async function getStaticPaths() {
  const posts = await getAllPosts();
  
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

const { post } = Astro.props;
---

<article>
  <h1>{post.title}</h1>
  <p>{post.content}</p>
</article>

4.2 传递 Props #

astro
---
// src/pages/products/[id].astro

export async function getStaticPaths() {
  const products = await getProducts();
  
  return products.map(product => ({
    params: { id: product.id.toString() },
    props: { 
      product,
      relatedProducts: await getRelatedProducts(product.id)
    },
  }));
}

interface Props {
  product: Product;
  relatedProducts: Product[];
}

const { product, relatedProducts } = Astro.props;
---

<div>
  <h1>{product.name}</h1>
  <p>价格: ${product.price}</p>
  
  <h2>相关产品</h2>
  {relatedProducts.map(p => (
    <div>{p.name}</div>
  ))}
</div>

4.3 多参数静态路径 #

astro
---
// src/pages/blog/[year]/[month]/[slug].astro

export async function getStaticPaths() {
  const posts = await getAllPosts();
  
  return posts.map(post => ({
    params: {
      year: post.date.getFullYear().toString(),
      month: (post.date.getMonth() + 1).toString(),
      slug: post.slug,
    },
    props: { post },
  }));
}

const { year, month, slug } = Astro.params;
const { post } = Astro.props;
---

<article>
  <h1>{post.title}</h1>
  <p>发布于 {year}年{month}月</p>
</article>

4.4 剩余参数静态路径 #

astro
---
// src/pages/docs/[...path].astro

export async function getStaticPaths() {
  return [
    { params: { path: 'intro' } },
    { params: { path: 'guide/getting-started' } },
    { params: { path: 'guide/advanced/config' } },
    { params: { path: 'api/reference' } },
  ];
}

const { path } = Astro.params;
---

<h1>文档: {path}</h1>

五、分页 #

5.1 分页路由 #

使用 [页码] 创建分页:

astro
---
// src/pages/blog/[page].astro

export async function getStaticPaths({ paginate }) {
  const posts = await getAllPosts();
  
  return paginate(posts, { pageSize: 10 });
}

const { page } = Astro.props;
---

<div>
  {page.data.map(post => (
    <article>
      <h2>{post.title}</h2>
    </article>
  ))}
  
  <nav>
    {page.url.prev && <a href={page.url.prev}>上一页</a>}
    <span>第 {page.currentPage} 页</span>
    {page.url.next && <a href={page.url.next}>下一页</a>}
  </nav>
</div>

5.2 分页对象 #

paginate 函数返回的分页对象:

typescript
interface Page {
  data: any[];           // 当前页数据
  currentPage: number;   // 当前页码
  lastPage: number;      // 最后一页
  url: {
    current: string;     // 当前页 URL
    prev: string | undefined;  // 上一页 URL
    next: string | undefined;  // 下一页 URL
  };
  total: number;         // 总数据量
}

5.3 分页示例 #

astro
---
// src/pages/blog/[page].astro
import Layout from '../../layouts/Layout.astro';

export async function getStaticPaths({ paginate }) {
  const posts = await getAllPosts();
  
  return paginate(posts, { 
    pageSize: 6,
    paramName: 'page'
  });
}

const { page } = Astro.props;
---

<Layout title={`博客 - 第 ${page.currentPage} 页`}>
  <div class="posts">
    {page.data.map(post => (
      <article class="post">
        <h2>{post.title}</h2>
        <p>{post.excerpt}</p>
      </article>
    ))}
  </div>
  
  <nav class="pagination">
    {page.url.prev && (
      <a href={page.url.prev} class="prev">← 上一页</a>
    )}
    
    <span class="page-info">
      第 {page.currentPage} / {page.lastPage} 页
    </span>
    
    {page.url.next && (
      <a href={page.url.next} class="next">下一页 →</a>
    )}
  </nav>
</Layout>

<style>
  .posts {
    display: grid;
    gap: 2rem;
  }

  .pagination {
    display: flex;
    justify-content: space-between;
    margin-top: 2rem;
    padding-top: 2rem;
    border-top: 1px solid #e5e7eb;
  }
</style>

六、重定向 #

6.1 编程式重定向 #

astro
---
// src/pages/old-page.astro

return Astro.redirect('/new-page');
---

6.2 条件重定向 #

astro
---
// src/pages/admin.astro

const user = Astro.locals.user;

if (!user) {
  return Astro.redirect('/login');
}
---

<h1>管理面板</h1>

6.3 配置重定向 #

astro.config.mjs 中配置:

javascript
import { defineConfig } from 'astro/config';

export default defineConfig({
  redirects: {
    '/old-blog': '/blog',
    '/old-blog/[slug]': '/blog/[slug]',
  },
});

七、404 页面 #

7.1 创建 404 页面 #

astro
---
// src/pages/404.astro
import Layout from '../layouts/Layout.astro';
---

<Layout title="页面未找到">
  <div class="not-found">
    <h1>404</h1>
    <p>抱歉,您访问的页面不存在。</p>
    <a href="/">返回首页</a>
  </div>
</Layout>

<style>
  .not-found {
    text-align: center;
    padding: 4rem 2rem;
  }

  h1 {
    font-size: 6rem;
    color: #dc2626;
  }
</style>

八、路由优先级 #

8.1 优先级规则 #

当多个路由匹配同一 URL 时,Astro 按以下优先级选择:

text
优先级从高到低:

1. 静态路由
   /about.astro

2. 动态路由
   /blog/[slug].astro

3. 剩余参数路由
   /docs/[...path].astro

8.2 示例 #

text
src/pages/
├── blog/
│   ├── index.astro      → /blog (静态,优先级最高)
│   ├── [slug].astro     → /blog/:slug (动态)
│   └── [...path].astro  → /blog/* (剩余参数,优先级最低)

九、路由元数据 #

9.1 获取当前路由信息 #

astro
---
// 任何页面或组件

// 当前 URL
const url = Astro.url;

// 路径名
const pathname = url.pathname;

// 查询参数
const searchParams = url.searchParams;

// 完整 URL
const href = url.href;
---

<p>当前路径: {pathname}</p>
<p>查询参数: {searchParams.toString()}</p>

9.2 当前页面检测 #

astro
---
// src/components/Navigation.astro

const currentPath = Astro.url.pathname;
const navItems = [
  { label: '首页', href: '/' },
  { label: '博客', href: '/blog' },
  { label: '关于', href: '/about' },
];
---

<nav>
  {navItems.map(item => (
    <a 
      href={item.href}
      class={currentPath === item.href ? 'active' : ''}
    >
      {item.label}
    </a>
  ))}
</nav>

十、最佳实践 #

10.1 路由组织 #

text
推荐结构:
src/pages/
├── index.astro
├── about.astro
├── blog/
│   ├── index.astro
│   ├── [page].astro
│   └── [slug].astro
├── docs/
│   └── [...path].astro
└── 404.astro

10.2 命名规范 #

text
✅ 好的命名:
├── [slug].astro      # 语义化参数名
├── [id].astro        # 简洁明了
├── [page].astro      # 分页参数

❌ 不好的命名:
├── [param].astro     # 不明确
├── [x].astro         # 无意义

十一、总结 #

Astro 路由核心要点:

text
┌─────────────────────────────────────────────────────┐
│                 路由核心要点                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  📁 文件路由   文件路径即 URL 路径                   │
│                                                     │
│  📄 静态路由   固定路径的页面                        │
│                                                     │
│  🔄 动态路由   [参数] 匹配动态路径                   │
│                                                     │
│  📄 静态生成   getStaticPaths 预定义路径            │
│                                                     │
│  📖 分页       paginate 函数处理分页                │
│                                                     │
│  🔀 重定向     redirect 方法跳转                    │
│                                                     │
└─────────────────────────────────────────────────────┘

下一步,让我们学习 布局组件,掌握页面布局的创建!

最后更新:2026-03-28