Next.js = 全栈框架。一个项目同时做后端(写 API)+ 前端(React 页面)+ 路由(URL 分发)。不用分开维护两个项目,部署也一次搞定。
| 维度 | Spring Boot | Next.js |
|---|---|---|
| 项目数量 | 至少 1 个后端 + 1 个前端 | 1 个 |
| 路由配置 | @RequestMapping 注解 | 文件路径即路由 |
| API 返回数据 | @RestController 返回 JSON | route.ts 返回 JSON |
| 页面渲染 | JSP / Thymeleaf / 前端框架 | 内置 React 服务端渲染 |
| 部署 | jar (200MB+) + 前端 dist | 一个 next build |
| 跨域 | 后端配 CORS | 同源没有跨域 |
my-store/ ├── app/ ← 核心目录:页面 + API 都在这里 │ ├── page.tsx → 首页 │ ├── layout.tsx → 全局布局(每个页面都套这个壳) │ ├── error.tsx → 全局错误页面(替代 500) │ ├── loading.tsx → 全局加载状态 │ ├── not-found.tsx → 全局 404 │ │ │ ├── api/ ← 后端接口全放这 │ │ └── products/ │ │ ├── route.ts → GET/POST /api/products │ │ └── [id]/route.ts → GET/PUT/DELETE /api/products/123 │ │ │ ├── products/ ← 商品相关页面 │ │ ├── page.tsx → /products(商品列表) │ │ └── [id]/page.tsx → /products/123(商品详情) │ │ │ └── admin/ ← 管理后台 │ └── products/page.tsx → /admin/products │ ├── components/ ← 可复用的 UI 组件(自己写的) │ ├── Header.tsx 导航栏 │ ├── ProductCard.tsx 商品卡片 │ └── CartBadge.tsx 购物车角标 │ ├── lib/ ← 工具函数 / 配置 │ └── prisma.ts Prisma 客户端(数据库连接) │ ├── prisma/ ← 数据库模型定义 │ └── schema.prisma 数据表结构 │ ├── public/ ← 静态资源(图片、字体等) │ └── logo.png │ ├── .env ← 环境变量(不上传 Git) ├── next.config.ts ← Next.js 配置文件 ├── tailwind.config.ts ← Tailwind 配置 ├── tsconfig.json ← TypeScript 配置 ├── package.json ← 依赖管理(相当于 pom.xml) └── postcss.config.js ← CSS 处理配置
对比 Java: Spring Boot 项目一般有 controller/service/dao/mapper/resources/templates 等一堆目录。
Next.js 只有 app/(页面+API)、components/(UI 组件)、lib/(工具代码),其他都是配置文件。
app/ ├── page.tsx → / 首页 ├── layout.tsx → 全局布局(所有页面共享) ├── error.tsx → 全局错误页面(替代 500 白页) ├── loading.tsx → 全局加载状态 ├── not-found.tsx → 全局 404 页面 │ ├── api/ ← 这个目录下全是后端接口 │ └── products/ │ ├── route.ts → GET/POST /api/products │ └── [id]/route.ts → GET/PUT/DELETE /api/products/123 │ ├── products/ │ ├── page.tsx → /products 商品列表页 │ └── [id]/ │ └── page.tsx → /products/123 商品详情页(动态路由) │ └── cart/ └── page.tsx → /cart 购物车页
// app/api/products/route.ts = @RestController @RequestMapping("/api/products") import { NextResponse } from 'next/server' import { prisma } from '@/lib/prisma' // GET → 查列表(@GetMapping) export async function GET() { const products = await prisma.product.findMany() return NextResponse.json(products) } // POST → 新增(@PostMapping) export async function POST(request: Request) { const { name, price, stock } = await request.json() const product = await prisma.product.create({ data: { name, price, stock } }) return NextResponse.json(product, { status: 201 }) }
// app/api/products/[id]/route.ts = @GetMapping("/api/products/{id}") export async function GET( request: Request, { params }: { params: { id: string } } ) { const product = await prisma.product.findUnique({ where: { id: params.id } }) if (!product) { return NextResponse.json({ error: '商品不存在' }, { status: 404 }) } return NextResponse.json(product) } export async function DELETE( request: Request, { params }: { params: { id: string } } ) { await prisma.product.delete({ where: { id: params.id } }) return NextResponse.json({ success: true }) }
作用:在服务器上渲染好 HTML 直接发给浏览器,不发 JS,速度更快。
// app/products/page.tsx — 商品列表页 import { prisma } from '@/lib/prisma' export default async function ProductsPage() { const products = await prisma.product.findMany({ orderBy: { createdAt: 'desc' } }) return ( <div> <h1>商品列表</h1> <div className="grid grid-cols-3 gap-4"> {products.map(p => ( <div key={p.id}> <h2>{p.name}</h2> <p>¥{p.price}</p> </div> ))} </div> </div> ) }
| 命令 | 用途 |
|---|---|
npx create-next-app@latest my-app | 创建新项目(选 TypeScript + App Router) |
npm run dev | 启动开发服务器 → http://localhost:3000 |
npm run build | 构建生产版本 |
npm run start | 启动生产服务器 |