Next.js 路由 = 文件路径
src/app/
├── page.tsx → /
├── about/
│ └── page.tsx → /about
├── blog/
│ ├── page.tsx → /blog
│ └── [slug]/
│ └── page.tsx → /blog/:slug
└── api/
└── hello/
└── route.ts → GET /api/hello
文件夹 = URL 段;page.tsx = 页面——零配置。
加一个新页面
新建 src/app/about/page.tsx:
export default function AboutPage() {
return (
<main className="p-8">
<h1 className="text-3xl font-bold">About Me</h1>
<p>I am WadeLy.</p>
</main>
);
}
启动 dev server,访问 http://localhost:3000/about。
动态路由
src/app/blog/[slug]/page.tsx → /blog/anything
src/app/posts/[year]/[month]/... → /posts/2026/05
// src/app/blog/[slug]/page.tsx
// Next.js 15+:params 是 Promise,组件必须 async 并 await
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return (
<article className="p-8">
<h1>Post: {slug}</h1>
</article>
);
}
访问 /blog/hello-world → 看到 "Post: hello-world"。
⚠️ Next.js 14 及更早
params是同步对象。15 起改为 Promise——这是为了支持流式渲染。searchParams同样变 Promise。
嵌套路由
src/app/
├── dashboard/
│ ├── layout.tsx ← 整个 dashboard 共享布局
│ ├── page.tsx → /dashboard
│ ├── users/
│ │ └── page.tsx → /dashboard/users
│ └── settings/
│ └── page.tsx → /dashboard/settings
Link:客户端路由
不要用 <a href>——用 Next 的 <Link> 才有客户端切换(不刷新页面 + 预加载):
import Link from 'next/link';
<Link href="/about">About</Link>
<Link href="/blog/hello">Read post</Link>
<Link href={`/users/${userId}`}>User</Link>
编程式跳转
'use client';
import { useRouter } from 'next/navigation';
export default function Form() {
const router = useRouter();
function handleSubmit() {
// 处理表单...
router.push('/success'); // 跳转
router.replace('/x'); // 替换历史
router.back(); // 后退
}
return <button onClick={handleSubmit}>Submit</button>;
}
查询参数
'use client';
import { useSearchParams } from 'next/navigation';
export default function SearchPage() {
const params = useSearchParams();
const q = params.get('q');
return <h1>Search: {q}</h1>;
}
// /search?q=hello → 显示 "Search: hello"
404 页面
src/app/not-found.tsx ← 全局 404
src/app/blog/not-found.tsx ← /blog/* 路径下的 404
// src/app/not-found.tsx
export default function NotFound() {
return (
<main className="p-8 text-center">
<h1 className="text-4xl">404</h1>
<p>Page not found</p>
<Link href="/" className="text-blue-600">Home</Link>
</main>
);
}
路由分组(不影响 URL)
src/app/
├── (marketing)/
│ ├── about/page.tsx → /about
│ └── pricing/page.tsx → /pricing
└── (dashboard)/
├── layout.tsx
└── users/page.tsx → /users
(name) 括号目录不出现在 URL 里——只用于组织 + 共享 layout。
API 路由
// src/app/api/hello/route.ts
export async function GET(request: Request) {
return Response.json({ message: 'Hello' });
}
export async function POST(request: Request) {
const body = await request.json();
return Response.json({ received: body });
}
访问 http://localhost:3000/api/hello → 返回 JSON。
Server Component vs Client Component
默认所有组件都是 Server Component——在服务端渲染,不能用 useState / useEffect / onClick。
需要交互的组件加 'use client':
'use client';
import { useState } from 'react';
export default function Counter() {
const [n, setN] = useState(0);
return <button onClick={() => setN(n + 1)}>{n}</button>;
}
下一篇 layouts + 11-fetch-data 讲 Server Component 的力量。
坑
- Next 13+ 的 App Router 跟老的 Pages Router 完全不同——新项目用 App Router
<a href>触发整页刷新——用<Link>才是 SPA 切换'use client'是文件级别——一旦标记,整个文件以及它 import 的子组件都是 Client- 动态路由
[slug]vs 必传[[slug]]vs 全部[...slug]——后面进阶细讲
下一篇:Layout 共享布局。