关于 app-router
介绍 Next.js App Router 路由模式的参考技能,帮助开发者使用 app 目录组织路由与布局,涵盖动态路由以及加载和错误状态。
主要功能
- 01路由与特殊文件约定
- 02动态路由和全匹配路由
- 03嵌套布局与路由组
- 04加载与错误状态
- 05静态和动态元数据
适用场景
- 01组织 Next.js 应用中的路由
- 02在不改变 URL 的情况下整理布局
- 03构建使用动态路由参数的页面
- 04添加加载和错误界面
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ davepoon/ buildwithclaude --skill app-router安装后在 Claude Code 中输入/app-router确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/dynamic-routes.mdparallel-routes.mdreferences/layouts-templates.mdloading-error-states.mdrouting-conventions.md详细介绍
Next.js App Router 模式
概述
App Router 是 Next.js 基于文件系统的路由器,构建于 React Server Components 之上。它使用 app/ 目录结构,其中的文件夹定义路由,特殊文件控制 UI 行为。
核心文件约定
路由文件
每个路由段由一个文件夹定义。文件夹中的特殊文件控制行为:
| 文件 | 用途 |
|---|---|
page.tsx | 路由的专属 UI,使路由可公开访问 |
layout.tsx | 共享 UI 外壳,在导航期间保留状态 |
loading.tsx | 使用 React Suspense 显示加载 UI |
error.tsx | 路由段的错误边界 |
not-found.tsx | 404 响应的 UI |
template.tsx | 类似 layout,但会在导航时重新渲染 |
default.tsx | 并行路由的回退内容 |
文件夹约定
| 模式 | 用途 | 示例 |
|---|---|---|
folder/ | 路由段 | app/blog/ → /blog |
[folder]/ | 动态段 | app/blog/[slug]/ → /blog/:slug |
[...folder]/ | 全捕获段 | app/docs/[...slug]/ → /docs/* |
[[...folder]]/ | 可选全捕获段 | app/shop/[[...slug]]/ → /shop 或 /shop/* |
(folder)/ | 路由组(不影响 URL) | app/(marketing)/about/ → /about |
@folder/ | 命名插槽(并行路由) | app/@modal/login/ |
_folder/ | 私有文件夹(不纳入路由) | app/_components/ |
创建路由
基本路由结构
要创建新路由,请添加一个包含 page.tsx 的文件夹:
app/
├── page.tsx # / (home)
├── about/
│ └── page.tsx # /about
└── blog/
├── page.tsx # /blog
└── [slug]/
└── page.tsx # /blog/:slug
页面组件
页面默认是 Server Component:
// app/about/page.tsx
export default function AboutPage() {
return (
<main>
<h1>About Us</h1>
<p>Welcome to our company.</p>
</main>
)
}
动态路由
通过 params 属性访问路由参数:
// app/blog/[slug]/page.tsx
interface PageProps {
params: Promise<{ slug: string }>
}
export default async function BlogPost({ params }: PageProps) {
const { slug } = await params
const post = await getPost(slug)
return <article>{post.content}</article>
}
布局
根布局(必需)
每个应用都需要一个包含 <html> 和 <body> 的根布局:
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
嵌套布局
布局会包裹其子级并保留状态:
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="flex">
<Sidebar />
<main className="flex-1">{children}</main>
</div>
)
}
加载和错误状态
加载 UI
使用 Suspense 创建即时加载状态:
// app/dashboard/loading.tsx
export default function Loading() {
return <div className="animate-pulse">Loading...</div>
}
错误边界
妥善处理错误:
// app/dashboard/error.tsx
'use client'
export default function Error({
error,
reset,
}: {
error: Error
reset: () => void
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={reset}>Try again</button>
</div>
)
}
路由组
在不影响 URL 结构的情况下组织路由:
app/
├── (marketing)/
│ ├── layout.tsx # Marketing layout
│ ├── about/page.tsx # /about
│ └── contact/page.tsx # /contact
└── (shop)/
├── layout.tsx # Shop layout
└── products/page.tsx # /products
元数据
静态元数据
// app/about/page.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'About Us',
description: 'Learn more about our company',
}
动态元数据
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: PageProps): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug)
return { title: post.title }
}
关键模式
- 就近放置:将组件、测试和样式放在路由附近
- 私有文件夹:使用
_folder存放非路由文件 - 路由组:使用
(folder)进行组织,且不影响 URL - 并行路由:使用
@slot构建复杂布局 - 拦截路由:使用
(.)模式创建模态框
资源
如需了解详细模式,请参阅:
references/routing-conventions.md- 完整的文件约定references/layouts-templates.md- 布局组合模式references/loading-error-states.md- Suspense 和错误处理examples/dynamic-routes.md- 动态路由示例examples/parallel-routes.md- 并行路由和拦截路由
评论
支持 Markdown