Next.js 入门指南:从零开始构建现代 Web 应用

Next.js 是一个基于 React 的全栈开发框架,由 Vercel 开发和维护。它提供了一系列强大的功能,帮助开发者构建高性能、可扩展的 Web 应用。本文将带你深入了解 Next.js,从基础概念到实践应用。

目录

  1. Next.js 简介
  2. 环境准备
  3. 核心概念
  4. 项目实战
  5. 高级特性
  6. 部署与优化
  7. 最佳实践

Next.js 简介

为什么选择 Next.js?

  • 开箱即用

    • 零配置路由系统
    • 内置开发服务器和构建工具
    • 自动代码分割和打包优化
    • 内置 CSS 和 Sass 支持
  • 性能优化

    • 自动图片优化和响应式支持
    • 字体加载优化
    • 代码分割和预加载
    • 智能构建优化
  • 灵活部署

    • 支持静态导出(Static Export)
    • 支持服务端渲染(SSR)
    • Vercel 平台一键部署
    • 支持自定义服务器
  • 开发体验

    • 快速刷新(Fast Refresh)
    • TypeScript 原生支持
    • 路由预加载
    • 开发时错误提示

核心特性

  1. 服务端渲染(SSR)

    • SEO 友好:搜索引擎可以抓取完整的页面内容
    • 更快的首屏加载:用户无需等待 JavaScript 加载完成
    • 更好的社交媒体分享:预渲染的 meta 标签
    • 支持动态数据:每次请求都可以获取最新数据
  2. 静态站点生成(SSG)

    • 构建时预渲染所有页面
    • 可以部署到任何静态托管服务
    • 极快的页面加载速度
    • 降低服务器成本
  3. 增量静态再生成(ISR)

    • 按需更新静态页面
    • 无需重新部署整个站点
    • 可以设置更新频率
    • 保持数据的新鲜度
  4. 混合渲染

    • 可以在同一应用中混合使用 SSG、SSR 和 ISR
    • 根据页面需求选择最适合的渲染方式
    • 优化用户体验和性能
    • 灵活的数据获取策略

环境准备

系统要求

  • Node.js

    • 版本要求:14.6.0 或更高
    • 推荐使用 LTS 版本
    • 需要包含 npm 包管理器
  • 开发环境

    • 支持的操作系统:MacOS、Windows、Linux
    • 推荐的代码编辑器:VS Code
    • 推荐的浏览器:Chrome 或 Firefox
  • 包管理器

    • npm(Node.js 自带)
    • yarn(可选)
    • pnpm(可选,更快的包管理器)

创建项目

  1. 使用 create-next-app
1
2
3
4
5
6
7
8
# 使用 npm
npx create-next-app@latest my-next-app

# 使用 yarn
yarn create next-app my-next-app

# 使用 TypeScript
npx create-next-app@latest my-next-app --typescript
  1. 配置选项
1
2
3
4
5
6
√ Would you like to use TypeScript? ... No / Yes
√ Would you like to use ESLint? ... No / Yes
√ Would you like to use Tailwind CSS? ... No / Yes
√ Would you like to use `src/` directory? ... No / Yes
√ Would you like to use App Router? ... No / Yes
√ Would you like to customize the default import alias? ... No / Yes
  1. 项目结构
1
2
3
4
5
6
7
my-next-app/
├── app/ # App Router 目录
├── components/ # 可复用组件
├── public/ # 静态资源
├── styles/ # CSS 样式文件
├── package.json # 项目配置
└── next.config.js # Next.js 配置

核心概念

1. 路由系统

Next.js 13+ 的 App Router 提供了基于文件系统的路由:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// app/layout.tsx - 根布局
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="zh">
<body>{children}</body>
</html>
)
}

// app/page.tsx - 首页
export default function Home() {
return (
<main>
<h1>欢迎来到 Next.js</h1>
</main>
)
}

// app/blog/[slug]/page.tsx - 动态路由
export default function BlogPost({
params,
}: {
params: { slug: string }
}) {
return <article>文章: {params.slug}</article>
}

2. 数据获取

Next.js 提供了多种数据获取方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 1. 服务端组件中获取数据
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>
{data.map(item => (
<div key={item.id}>{item.title}</div>
))}
</main>
)
}

// 2. 客户端数据获取
'use client'
import { useState, useEffect } from 'react'

export default function ClientComponent() {
const [data, setData] = useState(null)

useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(setData)
}, [])

return data ? <div>{data.title}</div> : <div>Loading...</div>
}

项目实战

让我们通过构建一个博客应用来实践 Next.js 的核心功能。

1. 项目初始化

1
2
npx create-next-app@latest blog-app --typescript --tailwind --app
cd blog-app

2. 创建页面组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// app/layout.tsx
import { Metadata } from 'next'
import './globals.css'

export const metadata: Metadata = {
title: '我的博客',
description: '使用 Next.js 构建的个人博客',
}

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="zh">
<body>
<nav className="bg-gray-800 text-white p-4">
<div className="container mx-auto">
<h1 className="text-xl font-bold">我的博客</h1>
</div>
</nav>
<main className="container mx-auto p-4">
{children}
</main>
</body>
</html>
)
}

3. 实现文章列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// app/page.tsx
import { getPosts } from '@/lib/posts'
import Link from 'next/link'

export default async function Home() {
const posts = await getPosts()

return (
<div className="grid gap-4">
{posts.map(post => (
<article key={post.id} className="border p-4 rounded-lg">
<h2 className="text-2xl font-bold">
<Link href={`/posts/${post.slug}`}>
{post.title}
</Link>
</h2>
<p className="text-gray-600 mt-2">{post.excerpt}</p>
</article>
))}
</div>
)
}

4. 添加 API 路由

1
2
3
4
5
6
7
8
// app/api/posts/route.ts
import { NextResponse } from 'next/server'
import { getPosts } from '@/lib/posts'

export async function GET() {
const posts = await getPosts()
return NextResponse.json(posts)
}

5. 实现文章详情页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// app/posts/[slug]/page.tsx
import { getPost } from '@/lib/posts'
import { notFound } from 'next/navigation'

export default async function PostPage({
params,
}: {
params: { slug: string }
}) {
const post = await getPost(params.slug)

if (!post) {
notFound()
}

return (
<article className="prose lg:prose-xl mx-auto">
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}

高级特性

1. 中间件使用

中间件可以在请求处理之前执行代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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('/admin')) {
return NextResponse.redirect(new URL('/login', request.url))
}

return NextResponse.next()
}

export const config = {
matcher: '/admin/:path*',
}

2. 图片优化

Next.js 提供了内置的图片优化组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import Image from 'next/image'

export default function Avatar() {
return (
<Image
src="/avatar.jpg"
alt="用户头像"
width={64}
height={64}
className="rounded-full"
priority
/>
)
}

3. 国际化路由

支持多语言路由配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// middleware.ts
import { match } from '@formatjs/intl-localematcher'
import Negotiator from 'negotiator'

let locales = ['en', 'zh']
let defaultLocale = 'zh'

function getLocale(request: Request): string {
const negotiatorHeaders: Record<string, string> = {}
request.headers.forEach((value, key) => (negotiatorHeaders[key] = value))

const languages = new Negotiator({ headers: negotiatorHeaders }).languages()
return match(languages, locales, defaultLocale)
}

export function middleware(request: Request) {
const pathname = request.nextUrl.pathname
const pathnameIsMissingLocale = locales.every(
locale => !pathname.startsWith(`/${locale}/`) && pathname !== `/${locale}`
)

if (pathnameIsMissingLocale) {
const locale = getLocale(request)
return Response.redirect(
new URL(`/${locale}${pathname}`, request.url)
)
}
}

部署与优化

1. 构建优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
domains: ['example.com'], // 允许的图片域名
},
experimental: {
serverActions: true,
},
webpack: (config, { dev, isServer }) => {
// 自定义 webpack 配置
return config
},
}

module.exports = nextConfig

2. 性能优化

  1. 代码分割
1
2
3
4
5
6
import dynamic from 'next/dynamic'

const DynamicComponent = dynamic(() => import('../components/Heavy'), {
loading: () => <p>加载中...</p>,
ssr: false // 禁用服务端渲染
})
  1. 预加载页面
1
2
3
4
5
6
7
8
9
10
11
12
13
import { useEffect } from 'react'
import { useRouter } from 'next/router'

export default function Page() {
const router = useRouter()

useEffect(() => {
// 预加载其他页面
router.prefetch('/about')
}, [router])

return <div>当前页面</div>
}

3. Vercel 部署

  1. 推送代码到 GitHub
  2. 在 Vercel 中导入项目
  3. 配置环境变量
  4. 自动部署

最佳实践

1. 目录结构

1
2
3
4
5
6
7
├── app/
│ ├── api/ # API 路由
│ ├── (auth)/ # 认证相关路由组
│ ├── components/ # 页面组件
│ └── lib/ # 工具函数
├── public/ # 静态资源
└── types/ # TypeScript 类型定义

2. 代码规范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用 TypeScript 类型
type Post = {
id: string
title: string
content: string
publishedAt: Date
}

// 使用服务端组件
// app/posts/page.tsx
import { getPosts } from '@/lib/posts'

export default async function PostsPage() {
// 直接在服务端获取数据
const posts = await getPosts()
return <PostList posts={posts} />
}

3. 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// app/error.tsx
'use client'

export default function Error({
error,
reset,
}: {
error: Error
reset: () => void
}) {
return (
<div className="text-center py-10">
<h2 className="text-2xl font-bold">出错了</h2>
<p className="text-red-500">{error.message}</p>
<button
onClick={() => reset()}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded"
>
重试
</button>
</div>
)
}

学习资源

结语

Next.js 是一个强大而灵活的框架,通过本文的学习,你应该已经掌握了其基本概念和使用方法。继续深入学习和实践,你将能够构建出高质量的现代 Web 应用。


本文将持续更新,欢迎关注最新的 Next.js 开发动态。