为什么要自建博客?
在此之前我用过 Hexo、WordPress,甚至手写过静态 HTML。它们各有各的好,但都有一个共同的问题:我不能完全控制体验。
我想要一个能自由设计每个像素的个人空间。框架博客做不到这些。
于是我决定从零搭一个。
技术选型
框架:Next.js 16 (App Router)
选 Next.js 而不是其他框架的理由很简单:Server Components。博客页面大部分是纯展示,不需要客户端 JS。RSC 让首屏加载几乎零 JS,只有交互部分才 hydrate。
样式:Tailwind CSS v4 + 自定义主题
v4 最大的变化是从配置文件(tailwind.config.ts)迁移到了纯 CSS 的 @theme 指令。配合 CSS 自定义属性,换主题只需要改几个变量的值。
数据库:SQLite → PostgreSQL(按需切换)
开发阶段 SQLite 零配置,部署前跑 bash scripts/switch-to-pg.sh 切换到 PostgreSQL。Prisma 做抽象层,切换数据库只需要改一行 provider。
认证:NextAuth v5 (Credentials + JWT)
Admin 系统需要认证。NextAuth v5 的 Credentials Provider 配合 JWT,轻量够用。没有 OAuth 的复杂跳转。
设计系统——黑金风格的诞生
最初的设计方案是深空蓝紫配色,像这样:
`css :root { --accent-cyan: #00d4ff; --accent-blue: #3b82f6; --accent-purple: #7c3aed; } `
后来觉得蓝紫太冷淡,换成了黑金。金色在暗色背景上有天然的奢华感,而且——说实话——金色代码更好看。
`css :root { --gold-dark: #8b6914; --gold-mid: #c99a2a; --gold-bright: #f5c71a; --gold-highlight: #ffe87c; } `
毛玻璃卡片
文章卡片的毛玻璃效果是用两层 linear-gradient 叠加 backdrop-filter: blur 实现的:
`css .glass-card { background: linear-gradient(145deg, rgba(8,12,30,0.65), rgba(15,12,25,0.55)), linear-gradient(135deg, rgba(139,105,20,0.08), rgba(245,199,26,0.03)); backdrop-filter: blur(16px); border: 1px solid rgba(245, 199, 26, 0.1); } `
注意:backdrop-filter 在 Safari 上有严重的渲染 bug,会让卡片做「圆周运动」。如果你的卡片在 Safari 上飘走了,去掉 backdrop-filter 就好。
奇门遁甲罗盘——最「不务正业」的 Hero
这个博客最引人注目的部分不是代码质量,而是首页那个旋转的奇门遁甲罗盘。
七层同心圆
罗盘由 7 层 SVG 同心圆环组成,从外到内分别是:
| 层 | 内容 | 旋转 |
|---|---|---|
| 1 | 先天八卦(乾兑离震巽坎艮坤) | 顺时针 120s |
| 2 | 十二地支(子丑寅卯辰巳午未申酉戌亥) | 逆时针 90s |
| 3 | 十天干(甲乙丙丁戊己庚辛壬癸) | 顺时针 70s |
| 4 | 后天八卦(离坤兑乾坎艮震巽) | 逆时针 55s |
| 5 | 九星(蓬任冲辅英芮柱心禽) | 顺时针 45s |
| 6 | 八门(休生伤杜景死惊开) | 逆时针 35s |
| 7 | 八神(符蛇阴合虎玄地天) | 顺时针 25s |
每一层都在以不同的速度旋转,中心是太极图。搭配轨道粒子和鼠标视差效果,形成独特的视觉标识。
踩坑:CSS class 冲突
最离谱的 bug 是这个:Tailwind v4 有一个内置 utility 叫 ring-1(设置 box-shadow),而我的罗盘环也用了 .ring-1 作为 class 名。
结果是 admin 页面所有用了 <Card> 组件的地方都继承了旋转动画——管理员在后台看到的文章卡片正在以 120 秒一圈的速度缓缓作圆周运动。
修复很简单:把 class 名改成 .hr-1(hero-ring 的缩写)。
架构决策
Server Component 直连 Prisma vs API 路由
前台页面全部用 Server Component 直连 Prisma,没有任何 API 中间层:
`typescript // app/(public)/blog/page.tsx — Server Component export default async function BlogPage() { const posts = await prisma.post.findMany({ where: { status: "PUBLISHED" }, orderBy: { publishedAt: "desc" }, }) return } `
这样做的理由:博客是读多写少的场景,Server Component 在服务端直接查库,不需要经过 HTTP 往返。
Admin 系统则用 API 路由,因为需要鉴权和写操作。
shadcn/ui 搭 Admin
Admin UI 全部用 shadcn/ui 的组件拼装。17 个组件(Button、Card、Table、Dialog 等)覆盖了所有后台需求,不需要自己写 UI 库。
移动端适配
最大的坑是 100vh。在手机上,浏览器的地址栏会动态改变视口高度——展开时 100vh 正确,收起时底部会有空白。
解决:用 100dvh 替代 100vh。
`css .snap-page { height: 100dvh; } `
Admin 侧边栏在 768px 以下从固定侧栏变为顶部导航条。
踩坑总结
1. ring-1 与 Tailwind utility 冲突
整个开发过程中最搞笑的 bug。Admin 卡片在做圆周运动。
2. backdrop-filter 在 Safari 上让元素「游泳」
macOS Safari 的 backdrop-filter 实现有 bug,会让应用了 blur 的元素在滚动时产生位移。解决方案:在 Safari 上不启用 backdrop-filter。
3. Middleware 里不能用 Prisma
Next.js 的 Middleware 运行在 Edge Runtime,不支持 Node.js 原生模块。Prisma 在 Edge 上只能用 WASM 引擎,但 SQLite 驱动不兼容。
解决方案:Middleware 只做简单的 cookie 检查,真正的鉴权在 API 路由里用 auth() 完成。
4. Streaming SSR 让 loading 状态复杂化
Server Component 的流式渲染意味着页面内容分段到达。如果 loading.tsx 没有 CSS 样式,用户会看到白屏。一个小金环 spinner 解决了这个问题。
总结
这个博客花了几周时间开发,从设计到部署全栈覆盖。最满意的地方不是代码,而是那个奇门遁甲罗盘——它让这个博客有了唯一性。
技术栈选型上,Next.js 16 App Router + Server Components 被证明是博客类网站的最佳选择:性能好、开发体验流畅、生态成熟。
所有代码都开源了。如果对某个实现细节感兴趣,欢迎在评论区留言讨论。