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 共享布局。