Nuxt: 页面与路由
最后更新:2026-08-26
Bob 在给 MegaShop 添加商品详情页时,手动配置了 50 条 vue-router 路由。新商品上架还得改路由文件——太痛苦了。Charlie 告诉他:Nuxt 3 用文件约定自动生成路由,创建文件就是创建路由。
1. 你将学到
- 文件路由:pages/ 目录到路由映射的完整规则
- 动态路由:[id].vue 与 [...slug].vue 参数捕获
- 嵌套路由与布局:父级页面 + 子级 slot 嵌套
- 编程式导航:navigateTo() / useRouter() / useRoute()
- MegaShop 多级动态路由实战
2. 一个管理员的真实故事
(1) 痛点:手动路由配置噩梦
Bob 用传统 Vue Router 给 MegaShop 配了 1 thousand 条路由——每个分类、每个商品页都是一条。每次上架新品都要改路由文件,一次漏配就 404。Alice 找不到商品,投诉率飙升。
(2) Nuxt 文件路由的解法
Nuxt 3 的文件路由让 Bob 只需创建文件:
TEXT
📖 仅展示
pages/products/[id].vue → /products/:id
一百万商品只需一个文件,动态参数自动传递。
(3) 收益:零配置路由
Bob 再也不用写路由配置了,新增页面就是新建文件,1 million 商品详情页只需一个 [id].vue。
3. 文件路由规则
(1) 目录到路由映射流程
flowchart LR
A[pages/index.vue] -->|"/"| B[Root Route]
C[pages/about.vue] -->|"/about"| C2[About Route]
D[pages/products/index.vue] -->|"/products"| D2[Product List Route]
E[pages/products/[id].vue] -->|"/products/:id"| E2[Product Detail Route]
F[pages/categories/[...slug].vue] -->|"/categories/:slug*"| F2[Catch-all Route]
(2) 文件路由映射规则
| 文件路径 | 生成路由 | 说明 |
|---|---|---|
| pages/index.vue | / | 首页 |
| pages/about.vue | /about | 静态页面 |
| pages/products/index.vue | /products | 商品列表 |
| pages/products/[id].vue | /products/:id | 动态参数 |
| pages/categories/[slug].vue | /categories/:slug | 动态参数 |
| pages/categories/[...slug].vue | /categories/:slug(*) | 通配路由 |
| pages/404.vue | /404 | 自定义 404 |
▶ 示例:基础页面路由
VUE
<!-- pages/index.vue -->
<template>
<div>
<h1>MegaShop - Millions of Products</h1>
<NuxtLink to="/products">Browse Products</NuxtLink>
</div>
</template>
输出:
TEXT
📖 仅展示
// 执行成功
VUE
<!-- pages/about.vue -->
<template>
<div>
<h1>About MegaShop</h1>
<p>Serving over 1 million products worldwide</p>
</div>
</template>
4. 动态路由
(1) 单参数动态路由
使用 [param] 语法定义动态路由参数。
▶ 示例:商品详情页动态路由
VUE
<!-- pages/products/[id].vue -->
<template>
<div>
<h1>Product #{{ route.params.id }}</h1>
<p>Price: ${{ product?.price }} USD</p>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
</script>
输出:
TEXT
📖 仅展示
// 执行成功
(2) 通配路由
使用 [...slug] 语法捕获多级路径。
▶ 示例:多级分类通配路由
VUE
<!-- pages/categories/[...slug].vue -->
<template>
<div>
<h1>Category: {{ slugPath }}</h1>
<p>{{ products.length }} products found</p>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
// /categories/electronics/headphones → slug = ['electronics', 'headphones']
const slugPath = computed(() => (route.params.slug as string[]).join('/'))
const { data: products } = await useFetch(`/api/categories/${slugPath.value}`)
</script>
输出:
TEXT
📖 仅展示
// 执行成功
(3) 动态路由参数类型
| 语法 | 匹配 | 示例 URL | params 值 |
|---|---|---|---|
| [id] | 单段 | /products/123 | { id: '123' } |
| [id].vue + index.vue | 可选 | /products 或 /products/123 | { id?: '123' } |
| [...slug] | 多段通配 | /a/b/c | { slug: ['a','b','c'] } |
5. 嵌套路由
(1) 嵌套路由结构
嵌套路由需要父级页面(同名的 .vue)和子级目录配合:
TEXT
📖 仅展示
pages/
├── products/
│ ├── index.vue # /products (child)
│ ├── [id].vue # /products/:id (child)
│ └── edit.vue # /products/edit (child)
└── products.vue # /products (parent with <NuxtPage />)
▶ 示例:商品页嵌套路由
VUE
<!-- pages/products.vue - Parent layout -->
<template>
<div>
<nav class="product-nav">
<NuxtLink to="/products">All Products</NuxtLink>
<NuxtLink to="/products/featured">Featured</NuxtLink>
</nav>
<!-- Child routes render here -->
<NuxtPage />
</div>
</template>
输出:
TEXT
📖 仅展示
// 执行成功
VUE
<!-- pages/products/index.vue - Child: product list -->
<template>
<div>
<h1>All Products</h1>
<p>1 million products available</p>
</div>
</template>
VUE
<!-- pages/products/[id].vue - Child: product detail -->
<template>
<div>
<h1>Product Detail</h1>
<p>ID: {{ route.params.id }}</p>
</div>
</template>
(2) 嵌套路由 vs Layout 对比
| 维度 | 嵌套路由 | Layout |
|---|---|---|
| 机制 | 父页面 + NuxtPage slot | layouts/ + NuxtLayout |
| 路由关系 | 父子路由共享布局 | 页面级布局切换 |
| 数据获取 | 父子各自 useFetch | Layout 不获取数据 |
| 适用场景 | 分类→商品→评论 层级 | 页面整体布局(Header/Footer) |
6. 编程式导航
(1) 三种导航方式对比
| API | 用途 | 服务端可用 | 场景 |
|---|---|---|---|
| navigateTo() | 编程跳转 | ✅ | 登录后重定向 |
| useRouter().push() | 客户端跳转 | ❌ | 按钮点击导航 |
| NuxtLink | 声明式链接 | ✅ | 模板中的导航 |
▶ 示例:navigateTo 服务端重定向
TYPESCRIPT
// In middleware or server-side context
export default defineNuxtRouteMiddleware((to) => {
const isAuthenticated = useState('isAuthenticated')
if (!isAuthenticated.value && to.path.startsWith('/admin')) {
return navigateTo('/login', { redirectCode: 302 })
}
})
输出:
TEXT
📖 仅展示
// 执行成功
▶ 示例:useRouter 客户端导航
VUE
<template>
<button @click="goToProduct(product.id)">
View Details
</button>
</template>
<script setup lang="ts">
const router = useRouter()
function goToProduct(id: number) {
router.push(`/products/${id}`)
}
</script>
输出:
TEXT
📖 仅展示
// 执行成功
▶ 示例:useRoute 获取参数
VUE
<script setup lang="ts">
const route = useRoute()
// Access params
const productId = route.params.id // From [id].vue
const categorySlug = route.params.slug // From [...slug].vue
// Access query params
// /products?category=electronics&sort=price
const category = route.query.category // 'electronics'
const sort = route.query.sort // 'price'
</script>
输出:
TEXT
📖 仅展示
// 执行成功
7. MegaShop 多级路由实战
(1) MegaShop 路由规划
| URL | 文件 | 功能 |
|---|---|---|
| / | pages/index.vue | 首页 |
| /products | pages/products/index.vue | 商品列表 |
| /products/[id] | pages/products/[id].vue | 商品详情 |
| /categories/[category] | pages/categories/[category].vue | 分类页 |
| /categories/[category]/[subcategory] | pages/categories/[...slug].vue | 子分类 |
| /cart | pages/cart.vue | 购物车 |
| /checkout | pages/checkout.vue | 结算页 |
| /admin | pages/admin/index.vue | 管理后台 |
| /admin/products | pages/admin/products/index.vue | 商品管理 |
| /404 | pages/404.vue | 404 页面 |
▶ 示例:MegaShop 404 页面
VUE
<!-- pages/404.vue -->
<template>
<div class="not-found">
<h1>404 - Product Not Found</h1>
<p>The product you're looking for doesn't exist.</p>
<NuxtLink to="/products">Browse all products</NuxtLink>
</div>
</template>
输出:
TEXT
📖 仅展示
// 执行成功
8. 综合示例:MegaShop 路由系统
VUE
<!-- pages/index.vue - Homepage -->
<template>
<div>
<section class="hero">
<h1>MegaShop</h1>
<p>Over 1 million products, delivered worldwide</p>
<NuxtLink to="/products">Shop Now</NuxtLink>
</section>
<section class="featured">
<ProductCard v-for="p in featured" :key="p.id" :product="p" />
</section>
</div>
</template>
<script setup lang="ts">
const { data: featured } = await useFetch('/api/products/featured')
</script>
VUE
<!-- pages/products/[id].vue - Product Detail -->
<template>
<div v-if="product">
<h1>{{ product.name }}</h1>
<p class="price">${{ product.price }} USD</p>
<button @click="addToCart">Add to Cart</button>
<button @click="goBack">Back to List</button>
</div>
<div v-else>
<p>Product not found</p>
<NuxtLink to="/products">Browse Products</NuxtLink>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
const router = useRouter()
const { data: product } = await useFetch(`/api/products/${route.params.id}`)
function addToCart() {
navigateTo('/cart')
}
function goBack() {
router.back()
}
</script>
❓ 常见问题
Q 动态路由的参数是字符串还是数字?
A URL 参数始终是字符串。需要数字时手动转换:
Number(route.params.id) 或在 useFetch 时由 API 返回类型决定。Q [id].vue 和 [...slug].vue 有什么区别?
A [id] 只匹配一段路径(/products/123),[...slug] 匹配多段(/a/b/c),slug 是数组。catch-all 路由适合无限层级分类。
Q 嵌套路由的父页面必须存在吗?
A 是的,父页面 products.vue 和子目录 products/ 同时存在时才形成嵌套。如果只要子路由,不要创建父页面。
Q NuxtLink 和普通 a 标签有什么区别?
A NuxtLink 做客户端导航(不刷新页面),a 标签做全页刷新。站内导航用 NuxtLink,外部链接用 a 标签。
Q 如何设置自定义 404 页面?
A 创建 pages/404.vue 即可。未匹配任何路由时 Nuxt 3 自动显示此页面。也可在 catch-all 路由中处理。
Q routeRules 和 pages 路由有什么关系?
A routeRules 在 nuxt.config.ts 中按路由路径配置渲染策略(SSR/ISR/CSR),不影响路由定义。路由由 pages/ 文件决定,routeRules 只控制渲染行为。
📖 小节
- Nuxt 3 文件路由:pages/ 下的文件自动映射为路由,零配置
- 动态路由用 [id].vue 捕获单参数,[...slug].vue 捕获多级路径
- 嵌套路由需同名父页面 + NuxtPage slot,与 Layout 是不同概念
- 编程式导航:服务端用 navigateTo(),客户端用 useRouter().push()
- MegaShop 百万商品只需一个 [id].vue 文件
📝 作业
- 基础题(难度⭐):创建 pages/about.vue 和 pages/contact.vue,验证路由是否自动生成
- 进阶题(难度⭐⭐):实现
/products/[category]/[id]两级动态路由,在页面中展示 category 和 id 参数 - 挑战题(难度⭐⭐⭐):实现一个 catch-all 路由 [...slug].vue,能够渲染
/docs/getting-started/installation这样的多级路径,并显示面包屑导航
---|