搭建个人博客的技术选型与踩坑记录
为什么一开始选择了这个组合
大三上学期学完前端三件套之后,我就想自己搭一个博客。当时身边同学大多用 Hexo 或者 WordPress,但我总觉得不够过瘾——模板改来改去还是受限,而且学不到什么底层的东西。于是决定从零开始,用 Next.js + Prisma + MySQL 自己写一套。
选型过程
第一个选择是框架。React 生态里能选的基本就两个方向:纯前端 SPA(CRA / Vite)或者全栈框架(Next.js / Remix)。博客需要 SEO 和首屏加载速度,所以我选了 Next.js 的 App Router——它的 Server Components 让我可以直接在组件里查数据库,不用单独写一套 API。
第二个选择是数据层。我需要一个 ORM 来管数据库,TypeORM 和 Drizzle 都考虑过,但 Prisma 7 的 schema-first 方式和自动生成的类型确实省了很多手写代码的时间。
第三个选择是数据库。本地开发用 SQLite 很爽(零配置),但服务器上要切 MySQL——宝塔面板一键创建数据库,用 127.0.0.1 本地连接就行。Prisma 用 driver adapter 模式同时支持两种数据库,切换起来只改一个文件。
项目结构
最终的文件组织是这样:
myblog/
├── src/
│ ├── app/
│ │ ├── layout.tsx ← 根布局,挂在字体和 Session Provider
│ │ ├── (resume)/ ← 简历站路由组,加括号不影URL
│ │ │ └── page.tsx ← 单页长滚动,Hero→Skills→Projects→Timeline
│ │ ├── blog/ ← 博客站
│ │ │ ├── layout.tsx ← 固定顶栏 + 页脚,毛玻璃效果
│ │ │ ├── page.tsx ← 文章列表 + 搜索 + 标签筛选
│ │ │ ├── posts/[slug]/ ← 文章详情 + TOC + 进度条
│ │ │ └── tags/[tag]/ ← 标签聚合页
│ │ └── admin/ ← 管理后台
│ │ ├── layout.tsx ← 侧边栏布局
│ │ ├── login/ ← NextAuth Credentials 登录
│ │ └── posts/new/ ← 文章编辑器(TipTap)
│ ├── components/
│ │ ├── ui/ ← 通用组件(BlurText, Magnet, Particles)
│ │ ├── resume/ ← 简历区块(Hero, Skills, Projects, Timeline)
│ │ └── blog/ ← 博客组件(SearchInput, TagFilter, ProgressBar, TOC)
│ └── lib/
│ ├── db.ts ← Prisma 单例,driver adapter 连接数据库
│ ├── auth.ts ← NextAuth v5,JWT + Credentials
│ ├── resume.ts ← 简历数据查询
│ ├── blog.ts ← 博客数据查询
│ └── i18n/ ← 中英双语字典(zh.ts / en.ts / types.ts)
├── prisma/
│ ├── schema.prisma ← 数据模型定义
│ └── seed.ts ← 种子数据
└── scripts/
└── copy-standalone-assets.cjs ← 构建后处理脚本
数据库建模
整个系统围绕几张核心表展开。用户表管登录,简历表存个人信息,博客表存文章。最有趣的是文章和标签的关系——用了一张联结表 PostTag 来实现多对多,这样一篇文章可以有多个标签,一个标签也能关联多篇文章。
除了基本的 User、ResumeProfile、Skill、Project、Achievement 这些简历相关的表之外,博客部分的核心模型如下:
BlogPost 是文章主体,每篇文章有一条独立的内容、摘要、封面和状态(draft/published/hidden),通过 slug 生成 URL。Category 是可选的分类树,Tag 通过 PostTag 联结表与文章建立多对多关系。SiteConfig 则是一个全局配置单例,存站点名、SEO 描述和自定义导航。
设计理念
博客站的视觉风格经历了一次比较大的调整。第一版用了很多动画和渐变,Framer Motion 满天飞,看起来炫但读起来累。第二版参考了一些技术博客的设计,把重心从"动"转向了"静"。
现在整个博客站只用了三种字体:衬线体做文章标题、无衬线体做正文、等宽字体做元数据和代码。颜色也只有三级灰阶,用 --t1 / --t2 / --t3 三个 CSS 变量控制信息层级。边框统一用 1px solid + 2px 圆角,替换了之前到处飞的阴影效果。动画几乎全砍了,只在必要的地方保留了 CSS transition,hover 时改一下 border-color,干净利落。
最重要的是,整套配色和间距都通过 CSS 变量驱动。以后如果要加暗色主题,只需要换一套变量值,不用动组件代码。
阅读进度条
文章详情页顶部的进度条是整站唯一保留的动效,实现其实很简单——监听 scroll 事件,算出一个比例,用 scaleX 变换渲染:
const [progress, setProgress] = useState(0);
useEffect(() => {
const handleScroll = () => {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
setProgress(docHeight > 0 ? Math.min(scrollTop / docHeight, 1) : 0);
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, []);
这里有个小细节:用 scaleX 而不是动态改 width。因为 transform 走的是浏览器的 composite 线程,不触发 layout 和 paint,性能开销几乎为零。加上 passive: true,浏览器就能放心开启 GPU 加速滚动。
国际化
博客站和简历站都支持中英文切换。实现方式是用 React Context 包一层 I18nProvider,底层就是两个字典文件 zh.ts 和 en.ts,切换语言时重新 setState,所有组件通过 useI18n hook 拿到当前语言的文本。
这个博客从第一行代码到上线大概花了两周时间。最有成就感的不是网站本身,而是整个过程——从选技术栈、设计数据模型、写组件、处理部署,每一个环节都逼着我去理解前端工程的全貌。如果你也在琢磨要不要自己搭一个博客,我的建议是:动手吧,比想象中学到的东西多得多。