Skip to main content

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();
});
});

参考资源