Next.js
Next.js 是一个基于 React 的全栈开发框架,提供了服务端渲染、静态生成、API 路由等强大功能。
核心特性
1. 路由系统
Next.js 14 引入了全新的 App Router,基于 React Server Components:
// app/page.tsx - 页面组件
export default function HomePage() {
return <h1>Welcome to Next.js</h1>;
}
// app/blog/[slug]/page.tsx - 动态路由
export default function BlogPost({ params }: { params: { slug: string } }) {
return <h1>Post: {params.slug}</h1>;
}
// app/layout.tsx - 布局组件
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>{children}</body>
</html>
);
}
2. 数据获取
// 服务端组件中获取数据
async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // 增量静态再生成
});
return res.json();
}
export default async function Page() {
const data = await getData();
return <main>{/* 使用数据 */}</main>;
}
// 客户端数据获取
'use client';
import { useQuery } from '@tanstack/react-query';
export default function ClientComponent() {
const { data } = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/api/todos').then(res => res.json()),
});
return <div>{/* 使用数据 */}</div>;
}
3. 服务器组件
// 服务器组件(默认)
export default async function ServerComponent() {
const data = await getData(); // 直接在服务器端获取数据
return <div>{data.title}</div>;
}
// 客户端组件
'use client';
export default function ClientComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
4. API 路由
// app/api/users/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
const users = await getUsers();
return NextResponse.json(users);
}
export async function POST(request: Request) {
const data = await request.json();
const newUser = await createUser(data);
return NextResponse.json(newUser, { status: 201 });
}
Pages Router(传统版本)
1. 基础路由
// pages/index.tsx - 首页
export default function Home() {
return <h1>Welcome to Next.js</h1>;
}
// pages/posts/[id].tsx - 动态路由
export default function Post({ post }) {
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}
// 获取动态路由参数
export async function getStaticPaths() {
const posts = await getPosts();
return {
paths: posts.map((post) => ({
params: { id: post.id },
})),
fallback: false,
};
}
export async function getStaticProps({ params }) {
const post = await getPost(params.id);
return {
props: {
post,
},
};
}
2. 数据获取方法
// 1. getStaticProps - 静态生成
export async function getStaticProps() {
const data = await fetch('https://api.example.com/data');
const posts = await data.json();
return {
props: {
posts,
},
revalidate: 60, // ISR: 60秒后重新生成
};
}
// 2. getServerSideProps - 服务端渲染
export async function getServerSideProps(context) {
const { req, res } = context;
const data = await fetch('https://api.example.com/data');
const posts = await data.json();
return {
props: {
posts,
},
};
}
// 3. 客户端数据获取
import useSWR from 'swr';
function Profile() {
const { data, error } = useSWR('/api/user', fetcher);
if (error) return <div>Failed to load</div>;
if (!data) return <div>Loading...</div>;
return <div>Hello {data.name}!</div>;
}
3. API 路由
// pages/api/posts.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method === 'GET') {
const posts = await getPosts();
res.status(200).json(posts);
} else if (req.method === 'POST') {
const post = await createPost(req.body);
res.status(201).json(post);
} else {
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
4. 自定义 App 和 Document
// pages/_app.tsx
import type { AppProps } from 'next/app';
import Layout from '../components/Layout';
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
// pages/_document.tsx
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html>
<Head>
<link rel="stylesheet" href="..." />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
5. 路由导航
// 1. Link 组件
import Link from 'next/link';
export default function Navigation() {
return (
<nav>
<Link
href="/posts/[id]"
as={`/posts/${post.id}`}
>
{post.title}
</Link>
</nav>
);
}
// 2. 编程式导航
import { useRouter } from 'next/router';
export default function Navigation() {
const router = useRouter();
return (
<button onClick={() => router.push('/posts/1')}>
Go to Post
</button>
);
}
6. 样式处理
// 1. CSS Modules
// styles/Home.module.css
.container {
padding: 2rem;
}
// pages/index.tsx
import styles from '../styles/Home.module.css';
export default function Home() {
return (
<div className={styles.container}>
<h1>Welcome</h1>
</div>
);
}
// 2. Styled JSX
export default function Button() {
return (
<div>
<button>Click me</button>
<style jsx>{`
button {
background: blue;
color: white;
}
`}</style>
</div>
);
}
7. 图片优化
import Image from 'next/image';
export default function Avatar() {
return (
<Image
src="/me.png"
alt="Picture of the author"
width={500}
height={300}
layout="responsive"
/>
);
}
8. 国际化
// next.config.js
module.exports = {
i18n: {
locales: ['en', 'fr', 'zh'],
defaultLocale: 'en',
},
};
// pages/index.tsx
import { useRouter } from 'next/router';
export default function IndexPage() {
const router = useRouter();
const { locale } = router;
return (
<div>
<h1>{locale === 'en' ? 'Welcome' : '欢迎'}</h1>
<button onClick={() => router.push('/', '/', { locale: 'zh' })}>
切换到中文
</button>
</div>
);
}
9. 环境变量
# .env.local
DB_HOST=localhost
DB_USER=myuser
NEXT_PUBLIC_ANALYTICS_ID=abcdef
// 使用环境变量
// 服务端
const dbHost = process.env.DB_HOST;
// 客户端
const analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID;
10. 错误处理
// pages/404.js - 自定义404页面
export default function Custom404() {
return <h1>404 - Page Not Found</h1>;
}
// pages/500.js - 自定义500页面
export default function Custom500() {
return <h1>500 - Server-side error occurred</h1>;
}
// pages/_error.js - 自定义错误页面
function Error({ statusCode }) {
return (
<p>
{statusCode
? `An error ${statusCode} occurred on server`
: 'An error occurred on client'}
</p>
);
}
App Router(新版本)
1. 路由系统
Next.js 14 引入了全新的 App Router,基于 React Server Components:
// app/page.tsx - 页面组件
export default function HomePage() {
return <h1>Welcome to Next.js</h1>;
}
// app/blog/[slug]/page.tsx - 动态路由
export default function BlogPost({ params }: { params: { slug: string } }) {
return <h1>Post: {params.slug}</h1>;
}
// app/layout.tsx - 布局组件
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>{children}</body>
</html>
);
}
2. 数据获取
// 服务端组件中获取数据
async function getData() {
const res = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // 增量静态再生成
});
return res.json();
}
export default async function Page() {
const data = await getData();
return <main>{/* 使用数据 */}</main>;
}
// 客户端数据获取
'use client';
import { useQuery } from '@tanstack/react-query';
export default function ClientComponent() {
const { data } = useQuery({
queryKey: ['todos'],
queryFn: () => fetch('/api/todos').then(res => res.json()),
});
return <div>{/* 使用数据 */}</div>;
}
3. 服务器组件
// 服务器组件(默认)
export default async function ServerComponent() {
const data = await getData(); // 直接在服务器端获取数据
return <div>{data.title}</div>;
}
// 客户端组件
'use client';
export default function ClientComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
4. API 路由
// app/api/users/route.ts
import { NextResponse } from 'next/server';
export async function GET() {
const users = await getUsers();
return NextResponse.json(users);
}
export async function POST(request: Request) {
const data = await request.json();
const newUser = await createUser(data);
return NextResponse.json(newUser, { status: 201 });
}
高级特性
1. 中间件
// 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) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: '/dashboard/:path*',
};
2. 图片优化
import Image from 'next/image';
export default function Gallery() {
return (
<Image
src="/images/photo.jpg"
alt="Description"
width={500}
height={300}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
priority
/>
);
}
3. 静态资源处理
// 字体优化
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
export default function Layout({ children }) {
return (
<html lang="en" className={inter.className}>
<body>{children}</body>
</html>
);
}
4. 环境变量
// .env.local
DATABASE_URL=postgresql://...
API_KEY=your-api-key
// 使用环境变量
const url = process.env.DATABASE_URL;
const apiKey = process.env.NEXT_PUBLIC_API_KEY; // 客户端可用
性能优化
1. 静态生成和增量静态再生成(ISR)
// 静态生成
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
// ISR
export const revalidate = 3600; // 每小时重新生成
export default async function Page() {
const data = await getData();
return <div>{data.content}</div>;
}
2. 流式渲染
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<h1>My Page</h1>
<Suspense fallback={<Loading />}>
<SlowComponent />
</Suspense>
</div>
);
}
3. 路由预加载
import { useRouter } from 'next/navigation';
export default function Navigation() {
const router = useRouter();
return (
<div
onMouseEnter={() => router.prefetch('/dashboard')}
>
Dashboard
</div>
);
}
部署和构建优化
1. 输出配置
// next.config.js
module.exports = {
output: 'standalone',
experimental: {
serverActions: true,
},
images: {
domains: ['example.com'],
},
};
2. 构建优化
// next.config.js
module.exports = {
webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
// 自定义 webpack 配置
return config;
},
};
最佳实践
1. 目录结构
app/
├── (auth)/ # 路由组
│ ├── login/
│ └── register/
├── api/ # API 路由
├── components/ # 共享组件
├── lib/ # 工具函数
├── styles/ # 样式文件
└── layout.tsx # 根布局
2. 数据获取模式
// 1. 服务器组件获取
async function getData() {
const res = await fetch('...');
return res.json();
}
// 2. SWR 客户端获取
'use client';
import useSWR from 'swr';
function Profile() {
const { data, error } = useSWR('/api/user', fetcher);
if (error) return <div>Failed to load</div>;
if (!data) return <div>Loading...</div>;
return <div>Hello {data.name}!</div>;
}
3. 错误处理
// app/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>
);
}
4. 认证集成
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import GithubProvider from 'next-auth/providers/github';
const handler = NextAuth({
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
});
export { handler as GET, handler as POST };
调试和测试
1. 调试配置
// .vscode/launch.json
{
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Next.js",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "dev"],
"port": 9229
}
]
}
2. 测试设置
// __tests__/home.test.tsx
import { render, screen } from '@testing-library/react';
import Home from '@/app/page';
describe('Home', () => {
it('renders a heading', () => {
render(<Home />);
const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toBeInTheDocument();
});
});