Next.js

技术栈
前端框架
nextjsreactssrssgisr全栈

概览

Next.js 技术栈概览

Next.js 是由 Vercel 开发的 React 全栈元框架,提供 SSR/SSG/ISR 渲染策略、文件路由、API Routes 和 Server Components,是 React 生态中构建生产级应用的事实标准。

解决什么问题

  • SEO 友好:SSR 让搜索引擎可以抓取页面内容,React SPA 做不到
  • 性能优化:静态生成(SSG)、增量静态再生(ISR)、图片优化、代码分割内置
  • 全栈能力:API Routes / Server Actions,同一项目写前后端
  • 开发体验:文件即路由、Fast Refresh、零配置 TypeScript

关键特性

  • App Router (v13+):基于 React Server Components 的新路由范式
  • 文件路由app/page.tsx/app/blog/[slug]/page.tsx/blog/:slug
  • Server Components:默认服务端渲染,零 JS 体积。'use client' 声明交互边界
  • Server Actions:直接用 async function 处理表单提交,无需 API 端点
  • 多种渲染:SSR / SSG / ISR / PPR(部分预渲染),按页面灵活配置
  • Middleware:Edge 中间件,认证/重定向/国际化在请求层处理

毕设场景

  • 全栈博客/内容管理系统(Next.js + MDX + Prisma)
  • 电商(Next.js Commerce + Stripe)
  • AI 聊天应用(Next.js + Vercel AI SDK + OpenAI)
  • 毕设展示网站(SSG 部署到 Vercel 免费)

安装

Next.js 安装指南

1. 环境准备

  • Node.js:>= 18.17.x(推荐 20 LTS)
  • 包管理器:npm / yarn / pnpm / bun
  • IDE:VS Code + Next.js 相关插件
  • Vercel 账号(可选):免费一键部署

2. 安装命令

创建项目

# 推荐:带 TypeScript + Tailwind + App Router
npx create-next-app@latest my-next-app --typescript --tailwind --app --eslint

# 或交互式选择
npx create-next-app@latest my-next-app
# → Would you like to use TypeScript? Yes
# → Would you like to use ESLint? Yes
# → Would you like to use Tailwind CSS? Yes
# → Would you like to use `src/` directory? Yes
# → Would you like to use App Router? Yes
# → Would you like to customize the default import alias? No

cd my-next-app
npm run dev
# → http://localhost:3000

项目结构

src/
  app/
    layout.tsx      # 根布局
    page.tsx         # 首页 (/)
    globals.css      # 全局样式
  components/        # 客户端组件放这里
public/              # 静态资源

3. 常见安装问题

Q: Node.js 版本过低?
A: Next.js 15 要求 Node.js >= 18.17。用 nvm install 20 切换版本

Q: 端口 3000 被占用?
A: npm run dev -- -p 3001npx next dev -p 3001

Q: App Router vs Pages Router?
A: 毕设新项目一律用 App Router(Next.js 13+ 默认),Pages Router 仅维护老项目

Q: 国内安装慢?
A: 设置镜像 npm config set registry https://registry.npmmirror.com

示例

Next.js 博客首页 — SSR + ISR

目标

用 Next.js App Router 实现博客首页:服务端渲染文章列表,ISR 每 60 秒增量再生,同时包含客户端搜索框。

完整代码

src/app/page.tsx

import { Suspense } from 'react'
import SearchInput from './SearchInput'

// 模拟从数据库/API获取文章
async function getPosts(): Promise<Post[]> {
  // 真实场景: return fetch('https://api.example.com/posts', { next: { revalidate: 60 } })
  return [
    { id: 1, title: 'Next.js 15 新特性解读', author: '张三', date: '2024-12-01', tags: ['Next.js'] },
    { id: 2, title: 'React Server Components 深度解析', author: '李四', date: '2024-11-28', tags: ['React'] },
    { id: 3, title: '用 Prisma + PostgreSQL 构建全栈应用', author: '王五', date: '2024-11-20', tags: ['数据库'] },
  ]
}

interface Post {
  id: number
  title: string
  author: string
  date: string
  tags: string[]
}

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

  return (
    <main className="min-h-screen bg-gray-50">
      {/* 头部 */}
      <header className="bg-white shadow-sm border-b">
        <div className="max-w-3xl mx-auto px-4 py-8">
          <h1 className="text-4xl font-extrabold text-gray-900">📝 我的博客</h1>
          <p className="text-gray-500 mt-2">ISR 每 60s 更新 · App Router · RSC</p>
        </div>
      </header>

      {/* 搜索栏(客户端组件) */}
      <Suspense fallback={<div>加载搜索框...</div>}>
        <SearchInput />
      </Suspense>

      {/* 文章列表(服务端渲染) */}
      <section className="max-w-3xl mx-auto px-4 py-8 space-y-4">
        {posts.map(post => (
          <article
            key={post.id}
            className="bg-white rounded-xl shadow-sm border p-6 hover:shadow-md transition-shadow"
          >
            <h2 className="text-xl font-semibold text-gray-900 mb-2">{post.title}</h2>
            <div className="flex items-center gap-3 text-sm text-gray-500">
              <span>✍️ {post.author}</span>
              <span>📅 {post.date}</span>
            </div>
            <div className="mt-3 flex gap-2">
              {post.tags.map(tag => (
                <span key={tag} className="bg-blue-100 text-blue-700 text-xs px-2 py-1 rounded-full">
                  {tag}
                </span>
              ))}
            </div>
          </article>
        ))}
      </section>
    </main>
  )
}

src/app/SearchInput.tsx

'use client'

import { useState } from 'react'

export default function SearchInput() {
  const [query, setQuery] = useState('')

  return (
    <div className="max-w-3xl mx-auto px-4 py-4">
      <input
        type="text"
        value={query}
        onChange={e => setQuery(e.target.value)}
        placeholder="🔍 搜索文章..."
        className="w-full px-4 py-3 border border-gray-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500 text-lg"
      />
      {query && (
        <p className="mt-2 text-sm text-gray-500">
          搜索: <strong>{query}</strong> (客户端状态)
        </p>
      )}
    </div>
  )
}

运行

npm install &;& npm run dev
# → http://localhost:3000

预期输出

  • 服务端渲染的文章列表(查看页面源代码可见完整 HTML)
  • 客户端搜索框(使用 'use client' 声明)
  • 60 秒 ISR 增量再生(revalidate: 60

教程

Next.js App Router 全栈之旅

背景

Next.js 是 React 生态的全栈元框架,由 Vercel 维护。App Router(v13+)是它的新路由范式,基于 React Server Components,默认在服务端执行。

核心概念

1. Server vs Client Components

// 默认:服务端组件(无 'use client' 声明)
// 可直接 async/await 数据库查询,不需要 useEffect
export default async function Page() {
  const data = await db.query('SELECT * FROM posts')
  return <div>{data.map(...)}</div>
}
'use client'
// 客户端组件:需要交互(useState/onClick/useEffect)
import { useState } from 'react'

export default function SearchInput() {
  const [query, setQuery] = useState('')
  return <input value={query} onChange={e => setQuery(e.target.value)} />
}

2. 文件路由

app/
  page.tsx              → /
  layout.tsx            → 根布局
  about/page.tsx        → /about
  blog/[slug]/page.tsx  → /blog/:slug
  api/posts/route.ts    → /api/posts (API 端点)
  loading.tsx           → 路由级加载态
  error.tsx             → 路由级错误边界

3. 渲染策略

策略 用途 配置
SSR 每请求渲染 默认(服务端组件无缓存即 SSR)
SSG 构建时生成 generateStaticParams()
ISR 定时增量再生 fetch(... { next: { revalidate: 60 } })export const revalidate = 60
PPR 部分预渲染 实验性

4. Server Actions

// 直接在服务端处理表单,无需建 API 端点
async function createPost(formData: FormData) {
  'use server'
  const title = formData.get('title')
  await db.post.create({ data: { title } })
}

export default function NewPost() {
  return (
    <form action={createPost}>
      <input name="title" />
      <button type="submit">创建</button>
    </form>
  )
}

5. 数据获取模式

// 服务端组件直接 await 数据库
export default async function PostsPage() {
  const posts = await prisma.post.findMany()
  return posts.map(p => <PostCard key={p.id} post={p} />)
}

// 客户端用 SWR / React Query

分步操作

npx create-next-app@latest grad-project --typescript --tailwind --app
cd grad-project
npm run dev

思考题

  1. Server Component 中能不能用 useState?为什么?
  2. layout.tsxtemplate.tsx 区别是什么?
  3. 何时用 Server Actions,何时用 API Routes?

毕设场景

  • 全栈博客/CMS
  • AI 聊天应用(Vercel AI SDK)
  • 电商(Next.js Commerce)
  • 部署到 Vercel 免费,含域名