这个站点是怎么搭起来的

一台 2 核小机器、一个 Go 二进制、一份 Next.js 静态产物,加上 Nginx 与 Let's Encrypt。记录这个站点从工具站改成个人站的技术选型与上线流程。

这个站点没有 CMS,也没有数据库驱动的前台渲染:前台是构建期生成的纯静态 HTML,后台是一个 Go 写的单二进制接口服务。这篇把它讲清楚,方便以后自己回看,也方便想搭个人站的人抄作业。

一、整体结构

浏览器 ──HTTPS──> Nginx ──┬── 静态文件(个人站 HTML/CSS/JS,构建期生成)
                          ├── /api/  ──> tools.linux(Go 单二进制,127.0.0.1:8080)
                          ├── /f/    ──> filesrv(文件服务,图片直链)
                          └── platform.ruilin.xin ──> platform-core 平台
  • 前台:Next.js 静态导出(output: "export"),所有页面在构建期渲染成 HTML,Nginx 直接吐文件;
  • 后台/admin 是同一份静态产物里的页面,登录后调 API,文章、站点配置都存在 MySQL 里;
  • 图片:走独立的文件服务,前台只拿到一个直链 URL,不占应用服务器的磁盘与内存。

二、为什么前台要用静态导出

个人站最大的流量来源是搜索引擎,而爬虫不想执行 JavaScript。静态导出的结果是:每一篇文章、每一个工具页,HTML 里就有完整正文,百度直接抓走即可。

代价是「内容变更 → 需要重新构建」。我的处理方式:

  1. 首页、列表页这类会变的区块,构建期写一份快照进 HTML,客户端挂载后再按接口刷新一次——读者看到的是最新的,爬虫拿到的是快照;
  2. 真正频繁变化的东西(博客正文)在构建时逐篇抓取,正文直接进 HTML;
  3. 构建末尾有一道产物自检:只要发现占位页、空区块,构建直接失败、不许发布(吃过亏,见下面)。

三、后端为什么坚持单二进制

一个静态编译的 Go 程序 + 一个 TOML 配置,由 supervisor 托管,重启就是几秒的事:

supervisorctl stop tools
install -m 0755 tools.linux /root/.../backend/tools.linux
supervisorctl start tools

它负责:接口(内容、搜索、留言、统计)、限流与防爆破、后台鉴权、以及“构建期的数据源”。 没有容器、没有编排,2 核 2G 的机器跑一个站绰绰有余。

四、上线流程

  1. 后端交叉编译 GOOS=linux GOARCH=amd64,scp 上传后替换二进制并重启;
  2. 前端 node scripts/build.mjs prod(构建前删掉 .next/cache,否则旧数据会被缓存进产物);
  3. 构建产物打 tar 上传,服务器上整目录替换前端目录;
  4. nginx -t && nginx -s reload
  5. 线上自检:首页 200、文章页有正文、sitemap 条数、接口正常。

五、踩过的坑

  • 构建期被限流打成空壳:接口按 IP 限流(10 req/s),而构建要并发抓 1600+ 个接口。结果 next build 显示成功,但 1500 多篇文章页里一多半是没有正文的占位壳。现在构建期改用本机后端连生产库,并且构建末尾强制自检,不合格直接失败。
  • 混合内容:切 HTTPS 后,数据库里还存着 http 的图片地址,浏览器直接拦掉不显示。所有落库的 URL 都要跟着域名一起改。
  • 验证文件放错目录:搜索引擎的验证文件以前放在前端目录里,而前端每次部署整目录替换——验证就失效了。现在统一放 /var/www/verify/,用 nginx 正则 location 直出。

六、后面想做的

  • 文章内的图片懒加载与图集排版;
  • 站内搜索覆盖博客(现在只搜知识库);
  • 部署脚本化:一条命令完成构建、自检、上传、替换、回滚。

站点是自己给自己写的,慢慢来。

返回博客列表