Next.js 15实战:我是如何用一个周末搭建出生产级博客系统的

引言
我还记得两个月前的某个周五晚上,公司项目刚上线,我打开电脑想学点新东西放松一下。Next.js 15刚发布不久,官方文档看了好几遍,各种教程视频也刷了不少,但总感觉停留在纸上谈兵的阶段——什么Server Actions、App Router,概念都懂,就是不知道实际项目里怎么用。
你肯定也有这种感觉吧?看完教程热血沸腾,关掉浏览器立马懵了,这东西到底能拿来干啥?
所以那个周末,我决定不再看教程了,直接撸起袖子做个真实的项目——一个全栈博客系统。说实话,一开始我也没信心能做出来,毕竟涉及前端、后端、数据库、部署,听起来挺吓人的。但两天后,当我看到自己搭建的博客成功部署到Vercel,Lighthouse性能评分达到96分的时候,那种成就感真的无法形容。
这篇文章我会把整个过程分享给你,不是那种复制粘贴代码就完事的教程,而是真正带你理解为什么要这么做,以及我踩过哪些坑。技术栈是Next.js 15 + Server Actions + Prisma + PostgreSQL,都是生产级的代码,不是玩具项目。
如果你也想在一个周末掌握Next.js全栈开发,那就跟着我的节奏来吧。
为什么选择Next.js 15?
选技术栈这事儿,我纠结了好久。
Next.js 15刚出的时候,我在社区看到有人吐槽”又TM更新了,学不动了”。老实讲,我当时也有点抵触,毕竟14还没完全搞明白呢。但仔细研究了一下新特性后,我发现这次升级是真的香。
Server Actions:告别API Routes的繁琐
以前做全栈开发,最烦的就是写API Routes。创建一个api/posts/route.ts,定义POST方法,处理请求体,返回响应…每次都是这套流程,写到吐。
Server Actions彻底改变了这个玩法。你只需要在函数前加个'use server',然后直接在组件里调用就行了。我第一次看到这个特性时,心里想的是”这不就是把后端代码写前端了吗?“但试了一次后——真香!
举个例子,以前创建博客文章要这么写:
// 老方法:需要创建API Route
// app/api/posts/route.ts
export async function POST(request: Request) {
const body = await request.json()
// 一堆处理逻辑...
}
// 前端还要fetch调用
const response = await fetch('/api/posts', {
method: 'POST',
body: JSON.stringify(data)
})现在直接这样:
// app/actions/post-actions.ts
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title')
const content = formData.get('content')
// 直接操作数据库,是不是很爽?
return await prisma.post.create({
data: { title, content }
})
}在组件里调用就跟调本地函数一样简单。用个生活化的比喻:以前是你要自己去餐厅取外卖(API Routes),现在是外卖直送到家(Server Actions)。
Turbopack让开发快了一倍不止
Next.js 15把Turbopack从实验性变成了稳定版。官方说本地服务器启动快76.7%,代码更新快96.3%,我一开始以为是营销数字,实际用了后发现——没吹牛。
我的项目大概有30多个组件,用Webpack的话启动要7-8秒,Turbopack直接压缩到2秒以内。改个代码,热更新几乎是瞬间完成。这对开发体验的提升,不是一点半点。
为什么这个技术栈适合博客?
选Next.js做博客,主要看中三点:
SSR/SSG天然优势——博客最重要的是SEO,Next.js的服务端渲染和静态生成天生就是为这个场景设计的。Google爬虫直接拿到完整的HTML,比纯客户端渲染的React强太多了。
Prisma的类型安全——我以前用过Mongoose写MongoDB,类型定义全靠手动维护,一不小心就出bug。Prisma直接从schema生成TypeScript类型,写代码的时候有完整的智能提示,基本不会出错。
Server Actions简化开发——不用写API Routes了,代码量至少减少30%。我这个博客系统,如果用传统方式写,估计要多写好几个route.ts文件。
技术栈选型与架构设计
搞清楚为什么后,接下来就是具体怎么做了。
我的完整技术栈
- 前端: Next.js 15 + TypeScript + Tailwind CSS
- 后端: Next.js Server Actions + NextAuth.js
- 数据库: PostgreSQL + Prisma ORM
- 部署: Vercel
你可能会想,为什么不用MongoDB?其实我一开始也这么想,MongoDB确实更灵活。但后来发现Prisma对PostgreSQL的支持更好,而且关系型数据库在处理博客文章、用户、评论这种关系明确的数据时,反而更合适。
目录结构长这样
my-blog/
├── app/
│ ├── (auth)/ # 认证相关页面
│ ├── blog/ # 博客相关页面
│ │ └── [slug]/ # 动态路由
│ ├── dashboard/ # 用户控制面板
│ ├── actions/ # Server Actions都放这里
│ └── api/auth/ # NextAuth配置
├── components/ # 可复用组件
├── lib/
│ ├── prisma.ts # Prisma单例(这个很重要!)
│ └── utils.ts # 工具函数
├── prisma/
│ └── schema.prisma # 数据库Schema
└── public/ # 静态资源这个结构我改了好几版才定下来。一开始我把Server Actions散落在各个页面文件里,后来发现维护起来太乱,专门建了个actions目录统一管理,清爽多了。
数据库Schema设计
数据库设计这块,我踩了个大坑。第一版我设计得过于复杂,什么标签、分类、阅读统计全加上了,结果写到一半发现根本用不上这么多表,又花了半天时间简化。
最后定下来的核心Schema是这样的:
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String?
image String?
posts Post[]
createdAt DateTime @default(now())
}
model Post {
id String @id @default(cuid())
title String
slug String @unique
content String @db.Text
published Boolean @default(false)
authorId String
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([slug])
@@index([authorId])
}注意看那两个@@index,这是性能优化的关键。博客列表页经常要按slug查询,用户主页要按authorId筛选,加上索引后查询速度能提升好几倍。
核心功能实现详解
好了,架构规划完,接下来是最刺激的部分——写代码!
3.1 环境搭建与初始化
创建Next.js 15项目超级简单:
npx create-next-app@latest my-blog
cd my-blog
npm install prisma @prisma/client zod next-auth安装完Prisma后,初始化:
npx prisma init这会生成prisma/schema.prisma和.env文件。在.env里配置PostgreSQL连接:
DATABASE_URL="postgresql://username:password@localhost:5432/myblog?schema=public"本地开发我用的是Docker跑PostgreSQL,一行命令搞定:
docker run --name blog-postgres -e POSTGRES_PASSWORD=mypassword -p 5432:5432 -d postgres3.2 Prisma数据库集成
配置好Schema后,运行migration:
npx prisma migrate dev --name init这里要特别注意一个坑——Prisma Client的单例模式。我第一次部署到Vercel的时候,完全忘了配置这个,结果网站跑了半天就报”Too many connections”错误,吓得我以为被DDoS攻击了。
原来是Next.js开发环境会热重载,每次重载都创建新的Prisma Client实例,连接池很快就爆了。正确的做法是这样:
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma
}这段代码看起来有点绕,但它保证了开发环境下只有一个Prisma实例,生产环境不受影响。记住了,这是Prisma官方推荐的最佳实践!
3.3 Server Actions实战应用
创建博客文章是核心功能,来看看Server Actions怎么写:
// app/actions/post-actions.ts
'use server'
import { prisma } from '@/lib/prisma'
import { revalidatePath } from 'next/cache'
import { z } from 'zod'
// 用Zod做数据验证,类型安全拉满
const PostSchema = z.object({
title: z.string().min(1, '标题不能为空').max(100),
content: z.string().min(10, '内容至少10个字'),
slug: z.string().regex(/^[a-z0-9-]+$/, 'slug只能包含小写字母、数字和横杠')
})
export async function createPost(formData: FormData) {
// 验证数据
const validatedFields = PostSchema.safeParse({
title: formData.get('title'),
content: formData.get('content'),
slug: formData.get('slug')
})
if (!validatedFields.success) {
return { error: '数据验证失败' }
}
try {
const post = await prisma.post.create({
data: {
...validatedFields.data,
authorId: 'user-id-here' // 实际项目从session获取
}
})
// 重新验证缓存,这样新文章立即显示
revalidatePath('/blog')
return { success: true, post }
} catch (error) {
return { error: '创建文章失败' }
}
}在组件里直接用:
// app/dashboard/new-post/page.tsx
import { createPost } from '@/app/actions/post-actions'
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" placeholder="标题" />
<textarea name="content" placeholder="内容" />
<input name="slug" placeholder="URL slug" />
<button type="submit">发布</button>
</form>
)
}看到没?不需要useState,不需要fetch,甚至不需要onSubmit事件处理。表单直接调用Server Action,Next.js自动处理序列化、网络请求、错误处理这些脏活累活。
一开始我也搞不清楚Server Actions和API Routes的区别,后来才明白:Server Actions就像”点外卖直送”,API Routes是”自己去餐厅取”。前者方便,后者灵活。大部分场景下,Server Actions够用了。
3.4 用户认证系统
认证这块我用的是NextAuth.js,配置起来挺简单:
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import GitHubProvider from 'next-auth/providers/github'
import { PrismaAdapter } from '@auth/prisma-adapter'
import { prisma } from '@/lib/prisma'
export const authOptions = {
adapter: PrismaAdapter(prisma),
providers: [
GitHubProvider({
clientId: process.env.GITHUB_ID!,
clientSecret: process.env.GITHUB_SECRET!
})
]
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }配置GitHub OAuth需要在GitHub Developer Settings创建一个应用,拿到Client ID和Secret填到.env里就行。整个过程10分钟搞定,比自己写JWT认证省事太多了。
3.5 SSR/SSG优化策略
这部分是性能优化的关键。说实话,一开始我全用SSR(服务端渲染),结果发现每次打开博客列表都要查数据库,完全没必要。后来改成混合策略:
博客列表页——SSG + ISR:
// app/blog/page.tsx
import { prisma } from '@/lib/prisma'
// 每小时重新生成一次
export const revalidate = 3600
export default async function BlogList() {
const posts = await prisma.post.findMany({
where: { published: true },
orderBy: { createdAt: 'desc' }
})
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.content.slice(0, 150)}...</p>
</article>
))}
</div>
)
}这个页面在构建时就生成了静态HTML,之后每小时自动重新生成。用户访问时直接拿静态文件,速度飞快。
博客详情页——动态SSR:
// app/blog/[slug]/page.tsx
export default async function Post({ params }: { params: { slug: string } }) {
const post = await prisma.post.findUnique({
where: { slug: params.slug }
})
if (!post) notFound()
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
)
}这个每次访问都会查数据库拿最新内容,适合经常更新的文章。
性能对比数据:
差距一目了然吧?这就是为什么Next.js适合做博客——SEO好,性能还强。
部署上线与优化
代码写完了,最激动人心的时刻到了——部署!
Vercel部署流程
我把代码推到GitHub后,直接在Vercel导入仓库。Vercel检测到是Next.js项目,自动配置好了一切。唯一要手动配置的是环境变量:
DATABASE_URL="你的生产数据库连接"
GITHUB_ID="OAuth Client ID"
GITHUB_SECRET="OAuth Secret"
NEXTAUTH_URL="https://your-domain.vercel.app"
NEXTAUTH_SECRET="随机字符串"生产数据库我用的是Supabase的免费PostgreSQL,每月有500MB额度,个人博客完全够用。
点击Deploy后,大概2分钟网站就上线了。第一次看到自己的域名能访问时,我盯着屏幕看了好几秒,然后忍不住截图发朋友圈。
生产环境优化
部署成功不代表结束,还有几个优化要做:
Prisma连接池配置:
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
// Vercel Serverless环境必须设置
directUrl = env("DIRECT_URL")
}Serverless环境下,每个函数调用都可能创建新连接,设置directUrl可以复用连接池。
图片优化:
import Image from 'next/image'
<Image
src="/avatar.jpg"
alt="用户头像"
width={100}
height={100}
// Next.js自动压缩优化
/>Next.js的Image组件会自动转换成WebP格式,按需加载,性能提升明显。
最终性能评分:
我用Lighthouse跑了一下,桌面端性能96分,移动端92分,SEO 100分。看到这个结果时,真的很有成就感——两天时间,从零到上线,还是生产级的性能。
扩展方向与学习资源
这个博客系统目前是MVP版本,还有很多可以加的功能:
- Markdown编辑器: 可以集成react-md-editor
- 评论系统: 考虑接入Giscus(基于GitHub Discussions)
- 搜索功能: Algolia或者自己用Prisma全文搜索
- 深色模式: 用next-themes很容易实现
我接下来打算先加个Markdown编辑器,写文章会方便很多。
推荐学习资源: - Next.js官方文档 - 永远是最权威的
- Prisma文档 - 把Getting Started看一遍就够了
- 这个项目的完整代码 - 可以直接clone下来跑
相信我,动手做一遍比看十遍文档有用。你学会了理论,但只有实战才能真正掌握。
总结
回顾一下,这个周末项目我学到了:
- Server Actions的强大: 真的可以替代大部分API Routes,开发效率提升不是一点半点
- Prisma的类型安全: 有了完整的TypeScript支持,写代码特别有安全感
- SSG/SSR的灵活性: 根据场景选择渲染策略,性能和SEO都能兼顾
- Vercel部署的简单: 从代码到上线,不到5分钟
老实讲,做完这个项目最大的收获不是技术本身,而是信心——原来全栈开发也没那么难,关键是要动手。
如果你也想搭建一个自己的博客系统,别犹豫,现在就开始吧!遇到问题很正常,我当时也是边查文档边写代码,踩了无数坑才跑通。但当你看到自己的作品上线的那一刻,所有的努力都值得。
希望这篇文章能帮你少走些弯路。有问题欢迎在评论区讨论,我会尽量回复。
期待看到你的作品!
用Next.js 15搭建生产级博客系统完整流程
从环境搭建到部署上线的完整步骤,包含Server Actions、Prisma、SSG/SSR优化等核心功能实现
⏱️ 预计耗时: 16 小时
- 1
步骤1: 环境搭建与项目初始化
创建Next.js 15项目:
• 运行:npx create-next-app@latest my-blog
• cd my-blog进入项目目录
安装依赖:
• npm install prisma @prisma/client zod next-auth
初始化Prisma:
• 运行:npx prisma init
• 生成prisma/schema.prisma和.env文件
配置数据库连接:
• 在.env里配置:
DATABASE_URL="postgresql://username:password@localhost:5432/myblog?schema=public"
本地开发使用Docker运行PostgreSQL:
• docker run --name blog-postgres -e POSTGRES_PASSWORD=mypassword -p 5432:5432 -d postgres - 2
步骤2: 配置Prisma数据库Schema
设计核心Schema:
User模型:
• id、email、name、image
• posts关系、createdAt字段
Post模型:
• id、title、slug、content、published
• authorId、author关系
• createdAt、updatedAt字段
关键优化:
• 在Post模型上添加@@index([slug])和@@index([authorId])索引
• 博客列表页经常要按slug查询
• 用户主页要按authorId筛选
• 加上索引后查询速度能提升好几倍
运行migration:
• npx prisma migrate dev --name init
• 创建数据库表结构
配置Prisma Client单例模式:
• 在lib/prisma.ts中实现全局单例
• 避免开发环境热重载时创建过多连接
• 防止"Too many connections"错误 - 3
步骤3: 实现Server Actions核心功能
创建Server Actions文件:
• 在app/actions/post-actions.ts中定义createPost函数
• 函数前加'use server'指令
数据验证:
• 使用Zod定义PostSchema(title、content、slug的验证规则)
• 用PostSchema.safeParse验证formData数据
数据库操作:
• 使用prisma.post.create创建文章
• authorId从session获取(实际项目中)
缓存更新:
• 使用revalidatePath('/blog')重新验证缓存
• 让新文章立即显示在列表页
组件调用:
• 在表单组件中直接使用action={createPost}
• 不需要useState、fetch、onSubmit事件处理
• Next.js自动处理序列化、网络请求、错误处理
Server Actions的优势:
• 代码量减少30%
• 不需要写API Routes
• 开发效率大幅提升 - 4
步骤4: 配置用户认证系统
安装NextAuth.js:
• 已通过npm install next-auth安装
创建认证路由:
• 在app/api/auth/[...nextauth]/route.ts中配置NextAuth
配置GitHub OAuth:
• 在GitHub Developer Settings创建应用
• 获取Client ID和Secret
配置Prisma Adapter:
• 使用PrismaAdapter(prisma)连接数据库
• 自动创建User、Account、Session等表
配置providers:
• 添加GitHubProvider
• 填入GITHUB_ID和GITHUB_SECRET环境变量
导出handler:
• export { handler as GET, handler as POST }
整个过程10分钟搞定,比自己写JWT认证省事太多 - 5
步骤5: 优化SSR/SSG渲染策略
博客列表页使用SSG+ISR:在app/blog/page.tsx中设置export const revalidate = 3600(每小时重新生成一次),使用prisma.post.findMany查询已发布的文章,按createdAt降序排列。这个页面在构建时就生成静态HTML,之后每小时自动重新生成,用户访问时直接拿静态文件,加载时间约200ms。博客详情页使用动态SSR:在app/blog/[slug]/page.tsx中每次访问都查数据库拿最新内容,使用prisma.post.findUnique({ where: { slug: params.slug } })查询,适合经常更新的文章,加载时间约800ms。性能对比:SSG页面~200ms,SSR页面~800ms,纯CSR~1500ms,差距一目了然。 - 6
步骤6: 部署到Vercel并优化
推送代码到GitHub:将项目代码推送到GitHub仓库。在Vercel导入项目:直接在Vercel导入GitHub仓库,Vercel检测到是Next.js项目自动配置好一切。配置环境变量:在Vercel项目设置中添加DATABASE_URL(生产数据库连接)、GITHUB_ID、GITHUB_SECRET、NEXTAUTH_URL(https://your-domain.vercel.app)、NEXTAUTH_SECRET(随机字符串)。生产数据库推荐使用Supabase免费PostgreSQL(每月500MB额度,个人博客完全够用)。Prisma连接池配置:在prisma/schema.prisma的datasource中添加directUrl = env("DIRECT_URL"),Serverless环境下每个函数调用都可能创建新连接,设置directUrl可以复用连接池。图片优化:使用Next.js的Image组件,自动转换成WebP格式,按需加载,性能提升明显。点击Deploy后约2分钟网站上线,最终Lighthouse性能分:桌面端96分,移动端92分,SEO 100分。
常见问题
Next.js 15的Server Actions和API Routes有什么区别?什么时候用哪个?
Server Actions的优势:
1) 代码量减少30%,不需要写API Routes
2) 直接在组件里调用,不需要useState、fetch、onSubmit事件处理
3) Next.js自动处理序列化、网络请求、错误处理
4) 类型安全,可以直接使用TypeScript类型
使用示例:在函数前加'use server',然后直接在组件里调用(<form action={createPost}>)。
API Routes的优势:
• 更灵活,可以处理复杂的请求逻辑
• 支持中间件
• 适合需要自定义HTTP响应的场景
大部分场景下,Server Actions够用了,只有在需要复杂HTTP处理时才用API Routes。
Turbopack的性能提升有多大?实际使用体验如何?
官方数据:
• 本地服务器启动快76.7%
• 代码更新快96.3%
实际使用体验:
• 我的项目大概有30多个组件,用Webpack的话启动要7-8秒,Turbopack直接压缩到2秒以内
• 改个代码,热更新几乎是瞬间完成
• 这对开发体验的提升,不是一点半点
Turbopack是Rust写的,比Webpack快很多,特别是在大型项目中优势更明显。如果你还在用Webpack,强烈建议升级到Next.js 15体验Turbopack。
Prisma Client单例模式为什么重要?不配置会有什么问题?
问题场景:
• Next.js开发环境会热重载,每次重载都创建新的Prisma Client实例
• 连接池很快就爆了,导致"Too many connections"错误
• 我第一次部署到Vercel的时候完全忘了配置这个,结果网站跑了半天就报这个错误,吓得我以为被DDoS攻击了
正确配置:
• 在lib/prisma.ts中实现全局单例:
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };
export const prisma = globalForPrisma.prisma ?? new PrismaClient();
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
这段代码保证了开发环境下只有一个Prisma实例,生产环境不受影响。记住了,这是Prisma官方推荐的最佳实践!
SSG、SSR、ISR有什么区别?如何选择?
• 构建时生成静态HTML,速度最快(~200ms)
• 适合内容不经常变化的页面(如博客列表页、关于页面)
SSR(Server-Side Rendering):
• 每次请求时在服务器生成HTML
• 适合需要实时数据的页面(如博客详情页、用户主页)
• 加载时间约800ms
ISR(Incremental Static Regeneration):
• SSG+定时重新生成,结合了SSG的速度和SSR的灵活性
• 适合内容偶尔更新的页面(如博客列表页每小时更新一次)
纯CSR(Client-Side Rendering):
• 在浏览器渲染,SEO差,加载时间~1500ms
• 不推荐用于博客
选择策略:
• 博客列表页用SSG+ISR(export const revalidate = 3600)
• 博客详情页用动态SSR
• 关于页面用纯SSG
性能对比:SSG~200ms,SSR~800ms,纯CSR~1500ms,差距一目了然。
为什么选择PostgreSQL而不是MongoDB?Prisma对PostgreSQL的支持如何?
1) Prisma对PostgreSQL的支持更好,类型安全、迁移工具、查询优化都很完善
2) 关系型数据库在处理博客文章、用户、评论这种关系明确的数据时,反而更合适
3) PostgreSQL的全文搜索功能强大,适合博客搜索场景
4) 生产环境稳定性更好,事务支持完善
Prisma的优势:
• 直接从schema生成TypeScript类型,写代码的时候有完整的智能提示,基本不会出错
• 我以前用过Mongoose写MongoDB,类型定义全靠手动维护,一不小心就出bug
• Prisma的类型安全特性让我写代码特别有安全感
如果你需要处理复杂的关系数据,PostgreSQL+Prisma是很好的选择。
Vercel部署Next.js项目需要注意什么?生产环境如何优化?
1) 推送代码到GitHub
2) 在Vercel导入GitHub仓库,Vercel检测到是Next.js项目自动配置好一切
3) 配置环境变量(DATABASE_URL、GITHUB_ID、GITHUB_SECRET、NEXTAUTH_URL、NEXTAUTH_SECRET)
4) 点击Deploy,约2分钟网站上线
生产环境优化:
1) Prisma连接池配置:
• 在prisma/schema.prisma的datasource中添加directUrl = env("DIRECT_URL")
• Serverless环境下每个函数调用都可能创建新连接,设置directUrl可以复用连接池
2) 图片优化:
• 使用Next.js的Image组件,自动转换成WebP格式,按需加载,性能提升明显
3) 生产数据库推荐使用Supabase免费PostgreSQL(每月500MB额度,个人博客完全够用)
最终性能评分:
• 我用Lighthouse跑了一下,桌面端性能96分,移动端92分,SEO 100分
• 看到这个结果时,真的很有成就感——两天时间,从零到上线,还是生产级的性能
13 分钟阅读 · 发布于: 2025年11月24日 · 修改于: 2026年1月22日
相关文章
Next.js 电商实战:购物车与 Stripe 支付完整实现指南

Next.js 电商实战:购物车与 Stripe 支付完整实现指南
Next.js 文件上传完整指南:S3/七牛云预签名URL直传实战

Next.js 文件上传完整指南:S3/七牛云预签名URL直传实战
Next.js 单元测试实战:Jest + React Testing Library 完整配置指南


评论
使用 GitHub 账号登录后即可评论