Skip to Content
Nextra 4.0 is released 🎉

Next.js 学习指南

Next.js 是一个基于 React 的全栈 Web 开发框架,提供了服务端渲染、静态站点生成、API 路由等功能,让 React 应用开发更加高效和强大。

🚀 快速开始

创建 Next.js 项目

# 使用 create-next-app npx create-next-app@latest my-next-app # 使用 TypeScript npx create-next-app@latest my-next-app --typescript # 使用 Tailwind CSS npx create-next-app@latest my-next-app --tailwind # 手动安装 npm install next react react-dom

基本项目结构

my-next-app/ ├── pages/ # 页面目录 │ ├── api/ # API 路由 │ ├── _app.js # 应用入口 │ ├── _document.js # 文档配置 │ └── index.js # 首页 ├── components/ # 组件目录 ├── public/ # 静态资源 ├── styles/ # 样式文件 ├── next.config.js # Next.js 配置 └── package.json

📚 核心概念

页面路由

Next.js 使用基于文件系统的路由:

// pages/index.js - 首页 (/) export default function Home() { return <h1>Welcome to Next.js!</h1>; } // pages/about.js - 关于页面 (/about) export default function About() { return <h1>About Us</h1>; } // pages/blog/[id].js - 动态路由 (/blog/1, /blog/2) export default function BlogPost({ post }) { return ( <article> <h1>{post.title}</h1> <p>{post.content}</p> </article> ); } // pages/blog/[category]/[id].js - 嵌套动态路由 export default function CategoryBlogPost({ post }) { return ( <article> <h1>{post.title}</h1> <p>Category: {post.category}</p> </article> ); }

数据获取

getStaticProps (SSG)

// pages/posts/[id].js export default function Post({ post }) { return ( <article> <h1>{post.title}</h1> <p>{post.content}</p> </article> ); } export async function getStaticProps({ params }) { const post = await fetchPost(params.id); return { props: { post }, revalidate: 60 // ISR: 60秒后重新生成 }; } export async function getStaticPaths() { const posts = await fetchPosts(); const paths = posts.map((post) => ({ params: { id: post.id.toString() } })); return { paths, fallback: 'blocking' // 或 false, true }; }

getServerSideProps (SSR)

// pages/profile.js export default function Profile({ user }) { return ( <div> <h1>Profile</h1> <p>Name: {user.name}</p> <p>Email: {user.email}</p> </div> ); } export async function getServerSideProps(context) { const { req, res, query } = context; // 检查认证 const token = req.cookies.token; if (!token) { return { redirect: { destination: '/login', permanent: false } }; } // 获取用户数据 const user = await fetchUser(token); return { props: { user } }; }

客户端数据获取

import { useState, useEffect } from 'react'; export default function Posts() { const [posts, setPosts] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchPosts() { try { const response = await fetch('/api/posts'); const data = await response.json(); setPosts(data); } catch (error) { console.error('Error fetching posts:', error); } finally { setLoading(false); } } fetchPosts(); }, []); if (loading) return <div>Loading...</div>; return ( <div> {posts.map(post => ( <article key={post.id}> <h2>{post.title}</h2> <p>{post.excerpt}</p> </article> ))} </div> ); }

⚡ API 路由

基本 API 路由

// pages/api/posts.js export default function handler(req, res) { const { method } = req; switch (method) { case 'GET': return getPosts(req, res); case 'POST': return createPost(req, res); default: res.setHeader('Allow', ['GET', 'POST']); res.status(405).end(`Method ${method} Not Allowed`); } } async function getPosts(req, res) { try { const posts = await fetchPostsFromDB(); res.status(200).json(posts); } catch (error) { res.status(500).json({ error: 'Failed to fetch posts' }); } } async function createPost(req, res) { try { const { title, content } = req.body; const post = await createPostInDB({ title, content }); res.status(201).json(post); } catch (error) { res.status(500).json({ error: 'Failed to create post' }); } }

动态 API 路由

// pages/api/posts/[id].js export default function handler(req, res) { const { method } = req; const { id } = req.query; switch (method) { case 'GET': return getPost(req, res, id); case 'PUT': return updatePost(req, res, id); case 'DELETE': return deletePost(req, res, id); default: res.setHeader('Allow', ['GET', 'PUT', 'DELETE']); res.status(405).end(`Method ${method} Not Allowed`); } } async function getPost(req, res, id) { try { const post = await fetchPostFromDB(id); if (!post) { return res.status(404).json({ error: 'Post not found' }); } res.status(200).json(post); } catch (error) { res.status(500).json({ error: 'Failed to fetch post' }); } }

中间件

// middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { // 检查认证 const token = request.cookies.get('token'); if (!token && request.nextUrl.pathname.startsWith('/dashboard')) { return NextResponse.redirect(new URL('/login', request.url)); } // 添加自定义头部 const response = NextResponse.next(); response.headers.set('x-custom-header', 'my-value'); return response; } export const config = { matcher: [ '/dashboard/:path*', '/api/:path*' ] };

🎯 组件和布局

自定义 App 组件

// pages/_app.js import Layout from '../components/Layout'; import '../styles/globals.css'; export default function MyApp({ Component, pageProps }) { return ( <Layout> <Component {...pageProps} /> </Layout> ); }

自定义 Document

// pages/_document.js import { Html, Head, Main, NextScript } from 'next/document'; export default function Document() { return ( <Html lang="zh-CN"> <Head> <link rel="icon" href="/favicon.ico" /> <meta name="description" content="My Next.js app" /> </Head> <body> <Main /> <NextScript /> </body> </Html> ); }

布局组件

// components/Layout.js import Header from './Header'; import Footer from './Footer'; export default function Layout({ children }) { return ( <div className="min-h-screen flex flex-col"> <Header /> <main className="flex-grow"> {children} </main> <Footer /> </div> ); }

🔧 样式解决方案

CSS Modules

/* styles/Button.module.css */ .button { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; } .primary { background-color: #0070f3; color: white; } .secondary { background-color: #f3f4f6; color: #374151; }
// components/Button.js import styles from '../styles/Button.module.css'; export default function Button({ children, variant = 'primary', ...props }) { return ( <button className={`${styles.button} ${styles[variant]}`} {...props} > {children} </button> ); }

Tailwind CSS

// components/Card.js export default function Card({ title, content, image }) { return ( <div className="max-w-sm rounded-lg shadow-lg bg-white overflow-hidden"> {image && ( <img className="w-full h-48 object-cover" src={image} alt={title} /> )} <div className="p-6"> <h3 className="text-xl font-semibold text-gray-900 mb-2"> {title} </h3> <p className="text-gray-600"> {content} </p> </div> </div> ); }

Styled Components

// components/StyledButton.js import styled from 'styled-components'; const StyledButton = styled.button` padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; background-color: ${props => props.variant === 'primary' ? '#0070f3' : '#f3f4f6'}; color: ${props => props.variant === 'primary' ? 'white' : '#374151'}; &:hover { opacity: 0.8; } `; export default function StyledButton({ children, variant = 'primary', ...props }) { return ( <StyledButton variant={variant} {...props}> {children} </StyledButton> ); }

📦 状态管理

Context API

// contexts/AuthContext.js import { createContext, useContext, useState } from 'react'; const AuthContext = createContext(); export function AuthProvider({ children }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(false); const login = async (credentials) => { setLoading(true); try { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) }); const data = await response.json(); setUser(data.user); } catch (error) { console.error('Login failed:', error); } finally { setLoading(false); } }; const logout = () => { setUser(null); }; return ( <AuthContext.Provider value={{ user, loading, login, logout }}> {children} </AuthContext.Provider> ); } export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within AuthProvider'); } return context; }

SWR 数据获取

// hooks/usePosts.js import useSWR from 'swr'; const fetcher = url => fetch(url).then(res => res.json()); export function usePosts() { const { data, error, mutate } = useSWR('/api/posts', fetcher); return { posts: data, isLoading: !error && !data, isError: error, mutate }; } // 在组件中使用 export default function Posts() { const { posts, isLoading, isError } = usePosts(); if (isLoading) return <div>Loading...</div>; if (isError) return <div>Error loading posts</div>; return ( <div> {posts.map(post => ( <article key={post.id}> <h2>{post.title}</h2> <p>{post.excerpt}</p> </article> ))} </div> ); }

🛠️ 配置和优化

Next.js 配置

// next.config.js /** @type {import('next').NextConfig} */ const nextConfig = { // 图片优化 images: { domains: ['example.com', 'cdn.example.com'], formats: ['image/webp', 'image/avif'] }, // 环境变量 env: { CUSTOM_KEY: process.env.CUSTOM_KEY }, // 重定向 async redirects() { return [ { source: '/old-blog/:slug', destination: '/blog/:slug', permanent: true } ]; }, // 重写 async rewrites() { return [ { source: '/api/:path*', destination: 'https://api.example.com/:path*' } ]; }, // 国际化 i18n: { locales: ['en', 'zh'], defaultLocale: 'en' } }; module.exports = nextConfig;

性能优化

// 动态导入 import dynamic from 'next/dynamic'; const DynamicComponent = dynamic(() => import('../components/HeavyComponent'), { loading: () => <p>Loading...</p>, ssr: false // 禁用服务端渲染 }); // 图片优化 import Image from 'next/image'; export default function OptimizedImage() { return ( <Image src="/hero.jpg" alt="Hero image" width={1200} height={600} priority placeholder="blur" blurDataURL="data:image/jpeg;base64,..." /> ); } // 字体优化 import { Inter } from 'next/font/google'; const inter = Inter({ subsets: ['latin'] }); export default function Layout({ children }) { return ( <div className={inter.className}> {children} </div> ); }

📖 学习资源

官方文档

实用工具和库

  • SWR: React Hooks 数据获取库
  • React Query: 强大的数据同步库
  • NextAuth.js: 身份验证解决方案
  • Prisma: 数据库 ORM
  • Vercel: 部署平台

🔍 调试工具

  • Next.js DevTools: 内置开发工具
  • React DevTools: React 组件调试
  • Vercel Analytics: 性能监控

最后更新: 2024年

最近更新:12/9/2025, 2:17:55 AM