Next.js 知识速查表
本页面汇总了 Next.js 编程中最常用的语法和知识点,方便快速查阅。
项目结构
app/
├── layout.tsx # 根布局
├── page.tsx # 首页 (/)
├── loading.tsx # 加载状态
├── error.tsx # 错误处理
├── not-found.tsx # 404 页面
├── globals.css # 全局样式
├── favicon.ico # 网站图标
├── api/ # API 路由
│ └── hello/
│ └── route.ts # /api/hello
├── blog/
│ ├── layout.tsx # 博客布局
│ ├── page.tsx # /blog
│ └── [slug]/
│ └── page.tsx # /blog/:slug
└── (group)/ # 路由组(不影响 URL)
└── page.tsx
路由
页面定义
// app/page.tsx
export default function Page() {
return <div>首页</div>;
}
// app/about/page.tsx
export default function AboutPage() {
return <div>关于我们</div>;
}
动态路由
// app/blog/[slug]/page.tsx
export default function BlogPost({
params,
}: {
params: { slug: string };
}) {
return <div>文章: {params.slug}</div>;
}
// 捕获所有路由
// app/docs/[...slug]/page.tsx
export default function DocsPage({
params,
}: {
params: { slug: string[] };
}) {
return <div>路径: {params.slug.join("/")}</div>;
}
查询参数
// Server Component
export default function SearchPage({
searchParams,
}: {
searchParams: { q?: string };
}) {
return <div>搜索: {searchParams.q}</div>;
}
// Client Component
"use client";
import { useSearchParams } from "next/navigation";
export default function SearchFilters() {
const searchParams = useSearchParams();
const q = searchParams.get("q");
return <div>搜索: {q}</div>;
}
导航
import Link from "next/link";
// Link 组件
<Link href="/">首页</Link>
<Link href="/blog/hello">文章</Link>
<Link href={{ pathname: "/search", query: { q: "test" } }}>搜索</Link>
// 编程式导航
"use client";
import { useRouter } from "next/navigation";
export default function Component() {
const router = useRouter();
router.push("/path"); // 导航
router.replace("/path"); // 替换
router.back(); // 返回
router.refresh(); // 刷新
}
布局
根布局
// app/layout.tsx
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "我的网站",
description: "网站描述",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="zh-CN">
<body>{children}</body>
</html>
);
}
嵌套布局
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex">
<nav>侧边栏</nav>
<main>{children}</main>
</div>
);
}
数据获取
Server Component
// 默认缓存
const res = await fetch("https://api.example.com/data");
// 禁用缓存
const res = await fetch("https://api.example.com/data", {
cache: "no-store",
});
// 定时重新验证
const res = await fetch("https://api.example.com/data", {
next: { revalidate: 60 },
});
// 标签重新验证
const res = await fetch("https://api.example.com/data", {
next: { tags: ["data"] },
});
重新验证
import { revalidateTag, revalidatePath } from "next/cache";
// 按标签重新验证
revalidateTag("data");
// 按路径重新验证
revalidatePath("/posts");
Client Component
"use client";
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
export default function Component() {
const { data, error, isLoading } = useSWR("/api/data", fetcher);
if (isLoading) return <div>加载中...</div>;
if (error) return <div>出错了</div>;
return <div>{data}</div>;
}
Server Actions
// app/actions.ts
"use server";
import { revalidatePath } from "next/cache";
export async function createPost(formData: FormData) {
const title = formData.get("title") as string;
await db.post.create({ data: { title } });
revalidatePath("/posts");
}
// 使用
<form action={createPost}>
<input name="title" />
<button type="submit">提交</button>
</form>
API 路由
// app/api/posts/route.ts
import { NextResponse } from "next/server";
export async function GET() {
const posts = await getPosts();
return NextResponse.json(posts);
}
export async function POST(request: Request) {
const body = await request.json();
const post = await createPost(body);
return NextResponse.json(post, { status: 201 });
}
// 动态路由
// app/api/posts/[id]/route.ts
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const post = await getPost(params.id);
return NextResponse.json(post);
}
元数据
// 静态元数据
export const metadata: Metadata = {
title: "页面标题",
description: "页面描述",
keywords: ["关键词1", "关键词2"],
};
// 动态元数据
export async function generateMetadata({
params,
}: {
params: { slug: string };
}) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
};
}
图片
import Image from "next/image";
// 固定尺寸
<Image src="/photo.jpg" alt="照片" width={400} height={300} />
// 填充模式
<div className="relative w-full h-64">
<Image src="/photo.jpg" alt="照片" fill className="object-cover" />
</div>
// 远程图片(需配置域名)
<Image src="https://example.com/photo.jpg" alt="照片" width={400} height={300} />
// 优先加载
<Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />
// 模糊占位符
<Image src="/photo.jpg" alt="照片" width={400} height={300} placeholder="blur" />
字体
import { Inter } from "next/font/google";
import localFont from "next/font/local";
// Google 字体
const inter = Inter({ subsets: ["latin"] });
// 本地字体
const myFont = localFont({
src: "./fonts/MyFont.woff2",
});
// 使用
<body className={inter.className}>{children}</body>
// CSS 变量
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
});
样式
// CSS Modules
import styles from "./Button.module.css";
<button className={styles.button}>点击</button>
// Tailwind CSS
<button className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
点击
</button>
// 条件样式
import clsx from "clsx";
<button className={clsx(
"px-4 py-2 rounded",
variant === "primary" && "bg-blue-500",
variant === "secondary" && "bg-gray-500"
)}>
点击
</button>
错误处理
// error.tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div>
<h2>出错了</h2>
<button onClick={reset}>重试</button>
</div>
);
}
// not-found.tsx
import Link from "next/link";
export default function NotFound() {
return (
<div>
<h2>页面未找到</h2>
<Link href="/">返回首页</Link>
</div>
);
}
// 触发 404
import { notFound } from "next/navigation";
if (!post) {
notFound();
}
中间件
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const token = request.cookies.get("token");
if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*"],
};
常用命令
# 开发
npm run dev
# 构建
npm run build
# 生产运行
npm run start
# 代码检查
npm run lint
# 类型检查
npx tsc --noEmit
配置文件
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "example.com",
},
],
},
env: {
CUSTOM_KEY: "value",
},
};
export default nextConfig;
环境变量
# .env.local
DATABASE_URL="postgresql://localhost:5432/mydb"
# 客户端可访问
NEXT_PUBLIC_API_URL="https://api.example.com"
// 服务端使用
console.log(process.env.DATABASE_URL);
// 客户端使用
console.log(process.env.NEXT_PUBLIC_API_URL);