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>
)
}这个每次访问都会查数据库拿最新内容,适合经常更新的文章。 性能对比数据:
- SSG页面: 加载时间 ~200ms
- SSR页面: 加载时间 ~800ms
- 纯CSR: 加载时间 ~1500ms 差距一目了然吧?这就是为什么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分钟 老实讲,做完这个项目最大的收获不是技术本身,而是信心——原来全栈开发也没那么难,关键是要动手。 如果你也想搭建一个自己的博客系统,别犹豫,现在就开始吧!遇到问题很正常,我当时也是边查文档边写代码,踩了无数坑才跑通。但当你看到自己的作品上线的那一刻,所有的努力都值得。 希望这篇文章能帮你少走些弯路。有问题欢迎在评论区讨论,我会尽量回复。 期待看到你的作品!
发布于: 2025年11月24日 · 修改于: 2025年12月4日


