Next.js Cheat Sheet - 快速参考指南,收录常用语法、命令与实践。
# 使用 create-next-app
npx create-next-app@latest my-app
# 使用 TypeScript
npx create-next-app@latest my-app --typescript
# 使用 Tailwind CSS
npx create-next-app@latest my-app --tailwind
# 完整选项
npx create-next-app@latest my-app --ts --tailwind --eslint --app --src-dir
my-app/
├── app/ # App Router 目录
│ ├── layout.tsx # 根布局
│ ├── page.tsx # 首页
│ ├── globals.css # 全局样式
│ ├── api/ # API 路由
│ │ └── route.ts
│ └── [slug]/ # 动态路由
│ └── page.tsx
├── components/ # 组件目录
├── lib/ # 工具函数
├── public/ # 静态资源
├── next.config.js # Next.js 配置
└── package.json
# 开发模式
npm run dev
# 构建生产版本
npm run build
# 启动生产服务器
npm run start
# 代码检查
npm run lint
| 文件 | 路由 |
|---|---|
app/page.tsx | / |
app/about/page.tsx | /about |
app/blog/[slug]/page.tsx | /blog/:slug |
app/shop/[...slug]/page.tsx | /shop/* |
app/(marketing)/about/page.tsx | /about (分组) |
// app/blog/[slug]/page.tsx
interface Props {
params: { slug: string };
}
export default function BlogPost({ params }: Props) {
return <h1>Post: {params.slug}</h1>;
}
// 生成静态参数
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map(post => ({
slug: post.slug
}));
}
// app/shop/[...slug]/page.tsx
// 匹配 /shop/a, /shop/a/b, /shop/a/b/c
interface Props {
params: { slug: string[] };
}
export default function Shop({ params }: Props) {
// /shop/a/b/c => slug = ['a', 'b', 'c']
return <div>{params.slug.join('/')}</div>;
}
app/
├── (marketing)/ # 不影响 URL
│ ├── about/page.tsx # /about
│ └── blog/page.tsx # /blog
├── (shop)/
│ └── cart/page.tsx # /cart
└── layout.tsx
// app/layout.tsx
export default function Layout({
children,
team,
analytics
}: {
children: React.ReactNode;
team: React.ReactNode;
analytics: React.ReactNode;
}) {
return (
<>
{children}
{team}
{analytics}
</>
);
}
// app/@team/page.tsx
// app/@analytics/page.tsx
// app/page.tsx
export default function Home() {
return (
<main>
<h1>Welcome to Next.js</h1>
</main>
);
}
// app/layout.tsx
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: 'My App',
description: 'My awesome app'
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={inter.className}>{children}</body>
</html>
);
}
// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="dashboard">
<nav>Dashboard Nav</nav>
<main>{children}</main>
</div>
);
}
// app/template.tsx
// 每次导航都会重新挂载
export default function Template({ children }: { children: React.ReactNode }) {
return <div>{children}</div>;
}
// app/dashboard/loading.tsx
export default function Loading() {
return (
<div className="loading">
<div className="spinner" />
<p>Loading...</p>
</div>
);
}
'use client';
// app/dashboard/error.tsx
export default function Error({
error,
reset
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</div>
);
}
// app/not-found.tsx
import Link from 'next/link';
export default function NotFound() {
return (
<div>
<h2>Not Found</h2>
<p>Could not find requested resource</p>
<Link href="/">Return Home</Link>
</div>
);
}
// 默认是服务端组件
async function getData() {
const res = await fetch('https://api.example.com/data');
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
}
export default async function Page() {
const data = await getData();
return <main>{data.title}</main>;
}
// 默认缓存 (等同于 SSG)
fetch('https://api.example.com/data');
// 不缓存 (等同于 SSR)
fetch('https://api.example.com/data', {
cache: 'no-store'
});
// 定时重新验证 (ISR)
fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // 1小时
});
// 基于标签重新验证
fetch('https://api.example.com/data', {
next: { tags: ['posts'] }
});
// app/actions.ts
'use server';
import { revalidatePath, revalidateTag } from 'next/cache';
// 重新验证路径
export async function updatePost() {
// 更新数据库
revalidatePath('/blog');
}
// 重新验证标签
export async function refreshPosts() {
revalidateTag('posts');
}
async function Page() {
// 并行请求
const [user, posts] = await Promise.all([getUser(), getPosts()]);
return (
<div>
<UserProfile user={user} />
<PostList posts={posts} />
</div>
);
}
'use client';
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(res => res.json());
export default function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error</div>;
return <div>Hello {data.name}</div>;
}
// app/actions.ts
'use server';
export async function createPost(formData: FormData) {
const title = formData.get('title');
const content = formData.get('content');
await db.post.create({
data: { title, content }
});
revalidatePath('/posts');
}
// app/page.tsx
import { createPost } from './actions';
export default function Page() {
return (
<form action={createPost}>
<input name="title" />
<textarea name="content" />
<button type="submit">Create</button>
</form>
);
}
'use server';
export async function updatePost(id: string, formData: FormData) {
const title = formData.get('title');
await db.post.update({
where: { id },
data: { title }
});
}
// 使用 bind
<form action={updatePost.bind(null, post.id)}>
<input name="title" />
<button>Update</button>
</form>;
'use client';
import { useFormState } from 'react-dom';
import { createPost } from './actions';
const initialState = { message: '' };
export default function Form() {
const [state, formAction] = useFormState(createPost, initialState);
return (
<form action={formAction}>
<input name="title" />
<button>Create</button>
<p>{state.message}</p>
</form>
);
}
'use client';
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? 'Submitting...' : 'Submit'}</button>;
}
// app/api/hello/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({ message: 'Hello' });
}
export async function POST(request: Request) {
const body = await request.json();
return NextResponse.json({ received: body });
}
// app/api/posts/[id]/route.ts
import { NextResponse } from 'next/server';
export async function GET(request: Request, { params }: { params: { id: string } }) {
const post = await getPost(params.id);
return NextResponse.json(post);
}
// app/api/search/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
// 查询参数
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get('q');
// Headers
const headersList = request.headers;
const token = headersList.get('authorization');
// Cookies
const cookieStore = request.cookies;
const session = cookieStore.get('session');
return NextResponse.json({ query });
}
export async function GET() {
return new NextResponse('Hello', {
status: 200,
headers: {
'Content-Type': 'text/plain',
'Cache-Control': 'no-store'
}
});
}
import { cookies } from 'next/headers';
export async function GET() {
const cookieStore = cookies();
// 读取
const token = cookieStore.get('token');
// 设置
cookieStore.set('token', 'value', {
httpOnly: true,
secure: true,
maxAge: 60 * 60 * 24 // 1 day
});
// 删除
cookieStore.delete('token');
return NextResponse.json({ success: true });
}
import Link from 'next/link';
export default function Nav() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/blog/hello-world">Blog Post</Link>
{/* 预取 */}
<Link href="/dashboard" prefetch={false}>
Dashboard
</Link>
{/* 替换历史 */}
<Link href="/login" replace>
Login
</Link>
</nav>
);
}
'use client';
import { useRouter } from 'next/navigation';
export default function Page() {
const router = useRouter();
return (
<div>
<button onClick={() => router.push('/dashboard')}>Dashboard</button>
<button onClick={() => router.replace('/login')}>Login</button>
<button onClick={() => router.back()}>Go Back</button>
<button onClick={() => router.refresh()}>Refresh</button>
</div>
);
}
'use client';
import { usePathname } from 'next/navigation';
export default function Nav() {
const pathname = usePathname();
return (
<nav>
<Link href="/" className={pathname === '/' ? 'active' : ''}>
Home
</Link>
</nav>
);
}
'use client';
import { useSearchParams } from 'next/navigation';
export default function Search() {
const searchParams = useSearchParams();
const query = searchParams.get('q');
return <div>Search: {query}</div>;
}
import { redirect } from 'next/navigation';
async function Page() {
const user = await getUser();
if (!user) {
redirect('/login');
}
return <div>Welcome {user.name}</div>;
}
// app/layout.tsx
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'My App',
description: 'My app description',
keywords: ['Next.js', 'React'],
authors: [{ name: 'Author' }],
openGraph: {
title: 'My App',
description: 'My app description',
images: ['/og-image.png']
},
twitter: {
card: 'summary_large_image'
}
};
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
interface Props {
params: { slug: string };
}
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
images: [post.image]
}
};
}
// app/layout.tsx
export const metadata: Metadata = {
title: {
template: '%s | My App',
default: 'My App'
}
};
// app/about/page.tsx
export const metadata: Metadata = {
title: 'About' // => "About | My App"
};
import Image from 'next/image';
export default function Page() {
return (
<>
{/* 本地图片 */}
<Image src="/hero.png" alt="Hero" width={800} height={600} priority />
{/* 远程图片 */}
<Image
src="https://example.com/photo.jpg"
alt="Photo"
width={400}
height={300}
placeholder="blur"
blurDataURL="data:image/..."
/>
{/* 填充容器 */}
<div style={{ position: 'relative', height: 300 }}>
<Image src="/bg.png" alt="Background" fill style={{ objectFit: 'cover' }} />
</div>
</>
);
}
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter'
});
const robotoMono = Roboto_Mono({
subsets: ['latin'],
variable: '--font-roboto-mono'
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
);
}
import Script from 'next/script';
export default function Page() {
return (
<>
{/* 延迟加载 */}
<Script src="https://example.com/script.js" strategy="lazyOnload" />
{/* 页面交互后加载 */}
<Script src="https://example.com/analytics.js" strategy="afterInteractive" />
{/* 内联脚本 */}
<Script id="inline-script">{`console.log('Hello')`}</Script>
</>
);
}
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// 重定向
if (request.nextUrl.pathname === '/old') {
return NextResponse.redirect(new URL('/new', request.url));
}
// 重写
if (request.nextUrl.pathname.startsWith('/api')) {
return NextResponse.rewrite(new URL('/api/proxy', request.url));
}
return NextResponse.next();
}
// 配置匹配路径
export const config = {
matcher: ['/old', '/api/:path*']
};
// 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*'
};
export function middleware(request: NextRequest) {
// 克隆请求头
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-custom-header', 'value');
// 返回带新头的响应
const response = NextResponse.next({
request: {
headers: requestHeaders
}
});
// 设置响应头
response.headers.set('x-response-header', 'value');
return response;
}
/** @type {import('next').NextConfig} */
const nextConfig = {
// 严格模式
reactStrictMode: true,
// 图片域名
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com'
}
]
},
// 环境变量
env: {
API_URL: process.env.API_URL
},
// 重定向
async redirects() {
return [
{
source: '/old',
destination: '/new',
permanent: true
}
];
},
// 重写
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'https://api.example.com/:path*'
}
];
},
// Headers
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY'
}
]
}
];
}
};
module.exports = nextConfig;
# .env.local
DATABASE_URL=postgresql://...
API_SECRET=secret
# 客户端可见 (需要 NEXT_PUBLIC_ 前缀)
NEXT_PUBLIC_API_URL=https://api.example.com
// 服务端
const dbUrl = process.env.DATABASE_URL;
// 客户端
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
# 安装 Vercel CLI
npm i -g vercel
# 部署
vercel
# 生产部署
vercel --prod
// next.config.js
const nextConfig = {
output: 'export'
};
# 构建静态文件
npm run build
# 输出到 out/ 目录
# Dockerfile
FROM node:18-alpine AS base
FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]
// next.config.js
const nextConfig = {
output: 'standalone'
};
'use client';
import { useRouter, usePathname, useSearchParams, useParams } from 'next/navigation';
export function Component() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const params = useParams();
// router.push('/path')
// router.replace('/path')
// router.back()
// router.refresh()
return null;
}
import { headers, cookies } from 'next/headers';
async function ServerComponent() {
// 读取请求头
const headersList = headers();
const userAgent = headersList.get('user-agent');
// 读取 cookies
const cookieStore = cookies();
const token = cookieStore.get('token');
return null;
}
地址
Level 10b, 144 Edward Street, Brisbane CBD(Headquarter)Level 2, 171 La Trobe St, Melbourne VIC 3000四川省成都市武侯区桂溪街道天府大道中段500号D5东方希望天祥广场B座45A13号Business Hub, 155 Waymouth St, Adelaide SA 5000Disclaimer
JR Academy acknowledges Traditional Owners of Country throughout Australia and recognises the continuing connection to lands, waters and communities. We pay our respect to Aboriginal and Torres Strait Islander cultures; and to Elders past and present. Aboriginal and Torres Strait Islander peoples should be aware that this website may contain images or names of people who have since passed away.
匠人学院网站上的所有内容,包括课程材料、徽标和匠人学院网站上提供的信息,均受澳大利亚政府知识产权法的保护。严禁未经授权使用、销售、分发、复制或修改。违规行为可能会导致法律诉讼。通过访问我们的网站,您同意尊重我们的知识产权。 JR Academy Pty Ltd 保留所有权利,包括专利、商标和版权。任何侵权行为都将受到法律追究。查看用户协议
© 2017-2025 JR Academy Pty Ltd. All rights reserved.
ABN 26621887572