MMyBlog
← 返回博客
Next.js
8 分钟·3,062 ·44 次阅读

搭建个人博客的技术选型与踩坑记录

为什么一开始选择了这个组合

大三上学期学完前端三件套之后,我就想自己搭一个博客。当时身边同学大多用 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 拿到当前语言的文本。


这个博客从第一行代码到上线大概花了两周时间。最有成就感的不是网站本身,而是整个过程——从选技术栈、设计数据模型、写组件、处理部署,每一个环节都逼着我去理解前端工程的全貌。如果你也在琢磨要不要自己搭一个博客,我的建议是:动手吧,比想象中学到的东西多得多。

文章链接: