Next.js:从 React 到全栈框架
1. Next.js 是什么?
1.1 传统 React SPA 的问题
传统的 React 应用是单页应用(SPA),它的工作流程:
- 浏览器下载一个几乎为空的 HTML 文件
- 下载 JavaScript 文件
- JavaScript 执行,渲染页面内容
这带来几个问题:
- 首屏加载慢:用户会先看到白屏,然后才看到内容
- SEO 不友好:搜索引擎爬虫抓取到的是空 HTML,无法索引页面内容
- 社交分享无预览:分享链接时,无法生成包含标题、描述、图片的卡片
React 本身只解决了“交互”问题,但没有解决“内容如何快速到达用户”的问题。
1.2 Next.js 的特性
Next.js 是 React 的全栈框架,它在 React 之上增加了:
1)服务端渲染(SSR):在服务器生成完整 HTML,直接返回给浏览器
2)静态站点生成(SSG):构建时生成 HTML,速度最快
3)增量静态再生(ISR):定时重新生成静态页面,兼顾速度和实时性
4)文件系统路由:根据 app 目录下的文件自动生成路由
5)API 路由:在同一个项目中编写后端接口
6)图片优化、字体优化:内置最佳实践
简单说:Next.js 让 React 应用可以直接运行在服务器上。
1.3 CSR vs SSR vs SSG vs ISR
| 渲染模式 | 渲染时机 | 优点 | 缺点 |
|---|---|---|---|
| CSR(客户端渲染) | 浏览器 | 交互丰富,适合后台 | 首屏慢,SEO 差 |
| SSR(服务端渲染) | 每次请求 | SEO 好,首屏快 | 服务器压力大 |
| SSG(静态生成) | 构建时 | 速度最快,可放 CDN | 内容更新需重新构建 |
| ISR(增量静态再生) | 构建时 + 定时刷新 | 兼顾速度和实时性 | 复杂度稍高 |
Next.js 默认使用 SSG + SSR 混合模式,你可以在每个页面选择最适合的渲染方式。
1.4 App Router
创建项目与项目结构
创建 Next.js 项目:
npx create-next-app@latest my-app --typescript --tailwind --app
App Router 目录结构:
my-app/
├── app/
│ ├── layout.tsx # 根布局(所有页面共享)
│ ├── page.tsx # 首页(/)
│ ├── about/
│ │ └── page.tsx # /about 页面
│ ├── blog/
│ │ ├── [slug]/
│ │ │ └── page.tsx # 动态路由 /blog/xxx
│ │ └── layout.tsx # 博客区域布局(可选)
│ └── api/
│ └── tasks/
│ └── route.ts # API 路由 /api/tasks
├── public/ # 静态资源
├── styles/
│ └── globals.css # 全局样式
└── next.config.js # Next.js 配置文件
关键约定:
page.tsx:定义页面组件,导出即可访问路由layout.tsx:包裹子页面的共享布局(如导航栏)[param]:动态路由参数,params对象会传入页面route.ts:定义 API 端点(GET、POST、PUT、DELETE)
2. 路由系统 App Router
2.1 文件即路由.js
文件即路由: page.js / layout.js
page.tsx:每个目录下的 page.tsx 对应一个 URL 路径。
| 文件路径 | 对应 URL |
|---|---|
app/page.tsx | / |
app/about/page.tsx | /about |
app/blog/page.tsx | /blog |
layout.tsx:同一目录下的共享布局,会包裹所有子页面。
// app/blog/layout.tsx
export default function BlogLayout({ children }: { children: React.ReactNode }) {
return (
<div>
<header>博客导航</header>
<main>{children}</main>
</div>
)
}
根布局(app/layout.tsx) 必须存在,包含 <html> 和 <body>。
2.2 动态路由
动态路由:[slug] / [...catchAll]
单段动态路由:app/blog/[slug]/page.tsx
// URL: /blog/hello-world
export default function BlogPost({ params }: { params: { slug: string } }) {
return <h1>文章:{params.slug}</h1>
}
可选全匹配:[[...slug]],如果路径为空,params.slug 为 undefined。
2.3 路由组 与平行路由
路由组 (group) 、平行路由 @folder
路由组:用括号 (folder) 组织代码,不影响 URL。
app/
├── (marketing)/
│ ├── about/page.tsx → /about
│ └── contact/page.tsx → /contact
└── (admin)/
└── dashboard/page.tsx → /dashboard
平行路由:用 @folder 命名,在同一页面同时渲染多个独立区域,解决布局复用问题。
app/
├── @analytics/
│ └── page.tsx # 平行路由插槽
├── @sidebar/
│ └── page.tsx
└── page.tsx
平行路由的典型场景:同时显示统计图表和侧边栏,且它们可以独立加载。
2.4 导航
导航: Link、useRouter
<Link> 组件:客户端页面切换,无刷新。
import Link from 'next/link'
export default function Nav() {
return (
<nav>
<Link href="/">首页</Link>
<Link href="/about">关于</Link>
</nav>
)
}
useRouter 钩子:编程式导航
'use client'
import { useRouter } from 'next/navigation'
export default function LoginButton() {
const router = useRouter()
return <button onClick={() => router.push('/dashboard')}>登录</button>
}
2.5 路由拦截与中间
路由拦截与中间件:Middleware
中间件在请求到达页面之前执行,常用于认证、日志、重定向。
创建 middleware.ts 放在项目根目录:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const token = request.cookies.get('token')?.value
const { pathname } = request.nextUrl
// 未登录用户访问 /dashboard 时重定向到 /login
if (!token && pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
// 配置中间件匹配哪些路径
export const config = {
matcher: ['/dashboard/:path*', '/profile/:path*']
}
中间件运行环境:Edge Runtime(轻量级),不能使用 Node.js 核心模块。
3. 渲染模式
3.1 服务端组件原理
在 Next.js 中,默认所有组件都是服务端组件(Server Components)。
服务端组件的特点:
1)在服务器上运行,生成 HTML 字符串发给浏览器
2)不能使用 useState、useEffect、浏览器 API(如 localStorage)
3)不能绑定事件(如 onClick)
4)可以直接 async/await 获取数据
5)代码不会被打包到客户端 JavaScript 中,体积更小
// app/user/page.tsx(默认就是服务端组件)
async function getUser(id: number) {
const res = await fetch(`https://api.example.com/user/${id}`)
return res.json()
}
export default async function UserPage() {
const user = await getUser(1)
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
)
}
为什么用服务端组件?
1)数据获取在服务器完成,浏览器无需等待 JS 加载
2)敏感逻辑(如 API token)不会暴露给客户端
3)减少客户端 JS 体积
3.2 客户端组件 与 'use client'
客户端组件 Client Components 、与 'use client'
需要交互、事件、状态的地方,必须标记为客户端组件。
在文件顶部写上 'use client':
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<button onClick={() => setCount(count + 1)}>
点击次数:{count}
</button>
)
}
可以同时使用服务端和客户端组件:父组件是服务端,子组件是客户端,数据通过 props 传递。
// app/page.tsx(服务端组件)
import ClientCounter from './ClientCounter'
export default async function Page() {
const initialCount = await getInitialCount()
return <ClientCounter initialCount={initialCount} />
}
// app/ClientCounter.tsx
'use client'
export default function ClientCounter({ initialCount }: { initialCount: number }) {
const [count, setCount] = useState(initialCount)
// ...
}
客户端组件的最佳实践:
- 尽量将客户端组件放在叶子节点(靠近 UI 层)
- 不要将整个页面变成客户端组件
- 通过 props 从服务端组件传递初始数据
3.3 静态渲染与动态渲染
静态渲染:Static Generation
动态渲染:Dynamic
静态渲染:在构建时生成 HTML,每次请求直接返回缓存内容。
// app/blog/[slug]/page.tsx
// 默认是静态渲染,前提是不使用动态函数(cookies、headers、searchParams)
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug) // 获取数据
return <div>{post.content}</div>
}
动态渲染:每次请求时在服务器上实时生成页面。
触发动态渲染的方式:
- 使用
cookies()、headers()函数 - 使用
searchParamsprop(查询参数) - 在
fetch中设置cache: 'no-store' - 在路由段配置
export const dynamic = 'force-dynamic'
// app/dashboard/page.tsx
export const dynamic = 'force-dynamic' // 强制动态渲染
export default async function Dashboard() {
const data = await fetch('https://api.example.com/dashboard', { cache: 'no-store' })
const dashboardData = await data.json()
return <div>...</div>
}
3.4 流式渲染与 Suspense
流式渲染:Streaming
流式渲染:将页面拆分成多个块,服务器一边生成一边发送给浏览器,用户不用等整个页面完成。
使用 <Suspense> 包裹异步组件:
// app/page.tsx
import { Suspense } from 'react'
import SlowComponent from './SlowComponent'
import FastComponent from './FastComponent'
export default function Page() {
return (
<div>
<FastComponent /> {/* 无需包裹,很快 */}
<Suspense fallback={<div>加载中...</div>}>
<SlowComponent /> {/* 这个慢,会先显示 fallback,加载后替换 */}
</Suspense>
</div>
)
}
流式渲染的效果:
- 用户不用看白屏,先看到部分内容
- 每个
Suspense边界独立加载 - 非常适合优化首屏体验
3.5 渲染模式的选择策略
| 页面类型 | 推荐模式 | 原因 |
|---|---|---|
| 营销页、博客文章 | 静态渲染(SSG) | 内容不常变,CDN 缓存,速度最快 |
| 产品列表(实时价格) | 动态渲染(SSR)+ ISR | 需要实时性,但可缓存短时间 |
| 用户仪表盘(个性化) | 动态渲染(SSR) | 每个用户数据不同,无法缓存 |
| 后台管理页 | 客户端渲染(CSR) | 首屏不关键,交互复杂 |
组合使用:一个页面可以混合不同渲染模式的组件。
- 导航栏、页脚 → 静态(服务端组件)
- 用户头像 → 动态(服务端组件,根据 cookie 显示)
- 交互按钮 → 客户端组件
4. 数据获取
4.1 服务端数据获取
服务端数据获取:async component
服务端组件可以直接使用 async/await 获取数据。
// app/users/page.tsx
async function getUsers() {
const res = await fetch('https://jsonplaceholder.typicode.com/users')
if (!res.ok) throw new Error('Failed to fetch users')
return res.json()
}
export default async function UsersPage() {
const users = await getUsers()
return (
<ul>
{users.map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
并行数据获取:使用 Promise.all 减少请求时间。
export default async function Dashboard() {
const [user, posts] = await Promise.all([
fetchUser(),
fetchPosts()
])
return (
<div>
<UserInfo user={user} />
<PostList posts={posts} />
</div>
)
}
4.2 客户端数据获
客户端组件中,推荐使用 SWR 或 TanStack Query 来处理数据获取和缓存。
安装 SWR:
npm install swr
'use client'
import useSWR from 'swr'
const fetcher = (url: string) => fetch(url).then(res => res.json())
export default function ClientPosts() {
const { data, error, isLoading } = useSWR('/api/posts', fetcher)
if (isLoading) return <div>加载中...</div>
if (error) return <div>加载失败</div>
return <ul>{data.map((post: any) => <li key={post.id}>{post.title}</li>)}</ul>
}
什么时候用客户端数据获取?
- 需要实时轮询数据(如股票价格)
- 数据变化频繁且只影响局部组件
- 不想通过 API 路由中转,直接调用第三方服务
4.3 缓存与重新验证
缓存与重新验证:fetch 的 cache 和 next.revalidate
Next.js 扩展了原生 fetch,增加了缓存控制选项。
默认静态缓存:
fetch('https://api.example.com/posts') // 等同于 { cache: 'force-cache' }
禁用缓存(每次实时获取):
fetch('https://api.example.com/posts', { cache: 'no-store' })
按时间重新验证(ISR 的核心):
// app/blog/[slug]/page.tsx
export default async function BlogPage({ params }: { params: { slug: string } }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`, {
next: { revalidate: 60 } // 每 60 秒重新验证一次
}).then(res => res.json())
return <div>{post.content}</div>
}
针对整个路由段设置缓存时间:
// app/blog/layout.tsx
export const revalidate = 60 // 该目录下所有页面 60 秒后重新生成
4.4 数据获取模式与最佳实践
模式1:服务端获取 + 静态生成(SSG)
适用于:博客文章、产品介绍页
优点:最快,可缓存到 CDN
配置:不设 cache: 'no-store',也不使用动态函数
模式2:服务端获取 + 增量静态再生(ISR)
适用于:新闻列表、商品目录
优点:兼顾实时性和性能
配置:fetch 中设置 next: { revalidate: 60 } 或路由段 export const revalidate = 60
模式3:客户端获取 + 缓存库
适用于:仪表盘、后台管理
优点:交互丰富,可配合轮询、乐观更新
配置:SWR 或 TanStack Query
最佳实践:
- 默认优先使用服务端组件 + 服务端获取
- 只在需要交互、实时数据时使用客户端获取
- 用
Suspense+ 流式渲染优化用户体验 - 避免在服务端组件中直接使用浏览器 API(localStorage、window)
5. API 路由 Route Handlers
5.1 创建 API 路由
创建 API 路由:route.js
在 Next.js 中,API 路由定义在 app/api/ 目录下,通过 route.ts 或 route.js 文件导出 HTTP 方法处理函数。
基本结构:
app/
└── api/
└── tasks/
└── route.ts # 处理 /api/tasks 请求
// app/api/tasks/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
return NextResponse.json({ message: '获取任务列表' })
}
export async function POST(request: Request) {
const body = await request.json()
return NextResponse.json({ message: '创建任务', data: body }, { status: 201 })
}
路由文件命名:必须叫 route.ts(或 route.js),不能随意命名。
5.2 请求与响应处理
1. 获取请求数据
import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
// 获取 JSON body
const body = await request.json()
const { title, completed } = body
// 获取查询参数
const searchParams = request.nextUrl.searchParams
const page = searchParams.get('page') || '1'
// 获取请求头
const authHeader = request.headers.get('authorization')
return NextResponse.json({ title, page })
}
2. 响应方法
// 返回 JSON
return NextResponse.json({ id: 1, name: '张三' })
// 自定义状态码
return NextResponse.json({ error: '未找到' }, { status: 404 })
// 返回文本
return new NextResponse('Hello World', {
status: 200,
headers: { 'Content-Type': 'text/plain' }
})
// 重定向
return NextResponse.redirect(new URL('/login', request.url))
5.3 动态 API 路由与参数
动态 API 路由通过文件夹命名 [param] 捕获 URL 参数。
app/
└── api/
└── tasks/
└── [id]/
└── route.ts # 处理 /api/tasks/123
// app/api/tasks/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server'
interface RouteParams {
params: { id: string }
}
export async function GET(
request: NextRequest,
{ params }: RouteParams
) {
const id = parseInt(params.id)
// 根据 id 获取数据...
return NextResponse.json({ id, title: '任务标题' })
}
export async function PUT(
request: NextRequest,
{ params }: RouteParams
) {
const id = parseInt(params.id)
const body = await request.json()
// 更新数据...
return NextResponse.json({ id, ...body })
}
export async function DELETE(
request: NextRequest,
{ params }: RouteParams
) {
const id = parseInt(params.id)
// 删除数据...
return NextResponse.json({ message: '删除成功' })
}
通配符路由:[...slug]/route.ts 捕获多段参数。
app/api/files/[...slug]/route.ts
// 请求 /api/files/images/logo.png → params.slug = ['images', 'logo.png']
5.4 中间件与 API 权限
中间件:Middleware
中间件可用于全局的 API 权限校验、日志记录、请求改写。
// middleware.ts(项目根目录)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// 只对 API 路由进行认证
if (request.nextUrl.pathname.startsWith('/api/')) {
const token = request.headers.get('authorization')
if (!token || token !== 'Bearer secret-token') {
return NextResponse.json({ error: '未授权' }, { status: 401 })
}
}
return NextResponse.next()
}
export const config = {
matcher: '/api/:path*'
}
在 API 路由中单独校验:
// app/api/posts/route.ts
import { NextResponse } from 'next/server'
import { verifyToken } from '@/lib/auth'
export async function POST(request: Request) {
const authHeader = request.headers.get('authorization')
const token = authHeader?.split(' ')[1]
if (!token || !verifyToken(token)) {
return NextResponse.json({ error: '未授权' }, { status: 401 })
}
// 继续处理...
}
6. 优化与配置
6.1 图片优化
图片优化:next/image
next/image 组件自动优化图片:懒加载、响应式尺寸、WebP 格式转换、防止布局偏移。
import Image from 'next/image'
export default function Avatar() {
return (
<Image
src="/avatar.png"
alt="用户头像"
width={100}
height={100}
priority // 首屏图片优先加载
/>
)
}
远程图片:需在 next.config.js 中配置允许的域名。
// next.config.js
module.exports = {
images: {
domains: ['images.unsplash.com', 'cdn.example.com'],
},
}
使用远程图片:
<Image
src="https://images.unsplash.com/photo-123"
width={800}
height={600}
alt="风景"
/>
6.2 字体优化
字体优化:next/font
next/font 自动优化字体,避免布局偏移(CLS)和额外的网络请求。
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
const robotoMono = Roboto_Mono({ subsets: ['latin'] })
export default function RootLayout({ children }) {
return (
<html lang="zh-CN" className={inter.className}>
<body>{children}</body>
</html>
)
}
局部使用:
import { Inter } from 'next/font/google'
const inter = Inter({ subsets: ['latin'] })
<h1 className={inter.className}>标题</h1>
自定义字体文件:
import localFont from 'next/font/local'
const myFont = localFont({
src: './fonts/MyFont.woff2',
display: 'swap',
})
6.3 脚本优化
脚本优化:next/script
next/script 控制第三方脚本的加载时机,优化性能。
import Script from 'next/script'
export default function Layout() {
return (
<>
<Script
src="https://www.googletagmanager.com/gtag/js"
strategy="afterInteractive" // 页面可交互后加载
/>
<Script
src="https://example.com/analytics.js"
strategy="lazyOnload" // 空闲时加载
onLoad={() => console.log('加载完成')}
/>
</>
)
}
strategy 选项:
beforeInteractive:在页面可交互前加载(用于关键脚本)afterInteractive:页面可交互后立即加载(默认)lazyOnload:浏览器空闲时加载worker:在 Web Worker 中加载(实验性)
6.4 元数据与 SEO
元数据与 SEO:metadata API
Next.js 提供了 Metadata API 来管理页面标题、描述、Open Graph 等。
静态元数据:
// app/about/page.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
title: '关于我们',
description: '了解我们的故事和团队',
openGraph: {
title: '关于我们',
description: '了解我们的故事和团队',
images: 'https://example.com/og-image.jpg',
},
}
export default function AboutPage() {
return <div>关于页面</div>
}
动态元数据(根据数据生成):
// app/blog/[slug]/page.tsx
import { Metadata } from 'next'
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`)
return res.json()
}
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: post.coverImage,
},
}
}
6.5 环境变量与配置文件
环境变量:.env.local、.env.development、.env.production
# .env.local
API_BASE_URL=https://api.example.com
NEXT_PUBLIC_GA_ID=G-XXXXXXXX
- 以
NEXT_PUBLIC_开头的变量会暴露给客户端 - 其他变量只能在服务端使用
使用方式:
// 服务端
const apiUrl = process.env.API_BASE_URL
// 客户端
const gaId = process.env.NEXT_PUBLIC_GA_ID
next.config.js 常用配置:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// 允许跨域图片
images: {
domains: ['images.unsplash.com'],
},
// 重定向
async redirects() {
return [
{
source: '/old-blog/:slug',
destination: '/blog/:slug',
permanent: true,
},
]
},
// 环境变量(不推荐,建议用 .env 文件)
env: {
CUSTOM_KEY: 'value',
},
}
module.exports = nextConfig
6.6 打包与部署
打包与部署:Vercel / 自托管
构建:
npm run build # 生成 .next 目录
npm start # 生产模式运行
部署到 Vercel(推荐):
- 将代码推送到 GitHub
- 登录 Vercel,导入仓库
- Vercel 自动检测 Next.js 并部署
- 每次
git push自动重新部署
环境变量在 Vercel 上配置:项目设置 → Environment Variables。
自托管(Node.js 服务器):
# 构建
npm run build
# 运行
npm start
需要安装 Node.js 并确保环境变量正确。
自托管(Docker):
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["npm", "start"]
静态导出(可选):
如果项目完全静态(无服务端逻辑),可以配置 output: 'export' 导出纯静态文件,部署到任何静态托管服务。
// next.config.js
module.exports = {
output: 'export',
}
7. 附录
7.1 Pages Router 与 App Router 对比
Next.js 提供了两种路由方式:传统的 Pages Router(pages/ 目录)和现代的 App Router(app/ 目录)。以下是它们的主要区别:
| 特性 | Pages Router | App Router |
|---|---|---|
| 引入版本 | Next.js 1.x | Next.js 13+ |
| 路由定义 | 文件名直接映射(pages/about.js → /about) | 文件夹 + page.js(app/about/page.js → /about) |
| 布局机制 | 需要手动包裹组件,无嵌套布局 | 内置 layout.js,支持嵌套布局 |
| 数据获取 | getServerSideProps, getStaticProps, getStaticPaths | async 组件 + fetch 扩展 |
| 渲染模式 | SSR, SSG, ISR | SSR, SSG, ISR, 流式渲染 |
| 服务端组件 | 不支持 | 默认(可减少客户端 JS) |
| 客户端组件 | 所有组件都是客户端组件 | 需标记 'use client' |
| API 路由 | pages/api/*.js | app/api/*/route.js |
| 中间件 | _middleware.js 或 middleware.js 在 pages 同级 | middleware.ts 在根目录,更灵活 |
| 元数据/SEO | 需要 next/head 或第三方库 | 内置 metadata API |
| 图片优化 | next/image | next/image(相同) |
| 动态路由 | [slug].js,[...slug].js | [slug]/page.js,[...slug]/page.js |
| 路由组 | X | (group) 用于组织代码 |
| 平行路由 | X | @folder 同时渲染多个页面 |
| 拦截路由 | X | (.)、(..)、(..)(..) 拦截同层级或上层路由 |
| 学习曲线 | 平缓 | 较陡(新概念较多) |
| 生产稳定性 | 成熟稳定 | 已稳定,推荐新项目使用 |
迁移建议:
新项目直接使用 App Router
旧项目可以逐步迁移,App Router 可以和 Pages Router 共存
7.2 常用命令速查表
| 命令 | 说明 |
|---|---|
npx create-next-app@latest | 创建新项目 |
npm run dev | 启动开发服务器(默认 http://localhost:3000) |
npm run build | 生产环境构建 |
npm start | 运行生产服务器(需要先 build) |
npm run lint | 运行 ESLint 检查 |
next info | 打印当前环境信息(用于问题排查) |
next build --debug | 构建时输出更多调试信息 |
next export | 导出静态站点(需配置 output: 'export') |
next telemetry | 启用/禁用匿名使用数据收集 |
常用 npm 脚本(package.json):
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
}
7.3 推荐学习资源
1. 官方文档
Next.js 中文文档(官方支持中文)
2. 视频教程
3. 实战学习
Next.js Learn 教程(免费) —— 官方互动教程
Vercel 模板市场 —— 各种开箱即用的 Next.js 项目模板
4. 社区与博客
Awesome Next.js —— 精选资源列表
5. 工具与插件
Vercel CLI —— 部署到 Vercel
Bundle Analyzer —— 分析打包体积
Next.js 完全指南到此结束!🎉
更多推荐
所有评论(0)