从零搭建个人网站:零依赖静态生成 + nginx 部署

一、为什么不用现成框架

面试季需要一个个人网站,要求很明确:内容以项目和博客为主、长期可维护、部署到一台只有 nginx 的小服务器(1 核 1G 内存)上,而且不给服务器装任何东西。

流行的方案各有代价:Hexo / Hugo 需要引入构建链和主题,Next.js / Nuxt 需要 Node 运行时常驻,VuePress / VitePress 最终也是静态产物但依赖树很深。对于一个嵌入式工程师来说,最熟悉的工具恰好就是最好的工具——我用 Node.js 内置模块写了一个零依赖静态站点生成器,全部代码不到一千行,构建一秒完成,产物是纯 HTML/CSS/JS。

二、整体架构

src/
├── data/            站点数据(JSON)
│   ├── site.json    名称、联系方式、导航
│   ├── projects.json 项目数据
│   ├── skills.json  技能分组
│   └── timeline.json 时间线
├── content/posts/   博客文章(Markdown + front matter)
├── assets/          CSS / JS / 图片
└── lib/md.mjs       Markdown 渲染器
build.mjs            构建入口:读数据 → 渲染页面 → 输出 dist/

数据与模板分离是核心决策:项目、技能、时间线全部是 JSON,博客全部是 Markdown 文件,加一篇博客只需新增一个 .md 文件然后重新构建,不碰任何代码。

三、几个关键实现

Markdown 渲染器

不引 marked / markdown-it,自己写一个两百行的渲染器。安全策略是先整体 HTML 转义,再处理标记,从根上杜绝用户内容注入:

js
let text = escapeHtml(src.replace(/\r\n/g, '\n'));
text = text.replace(/
(\w)\n([\s\S]?)```/g, (m, lang, code) => { // 代码块内容此时已转义,直接包 pre 即可 });


支持标题(自动生成锚点 id)、表格、代码块(带语言标注)、列表、引用等博客刚需语法。行内代码用占位符保护,避免内部标记被二次处理。

### 零动态内存的工程习惯迁移

写固件时养成的习惯不知不觉带到了网站:容量全部编译期定长、解析器是逐行状态机而不是递归下降、没有正则灾难性回溯。一个手写状态机解析 Markdown,比引入一个 200KB 的库更让人安心。

### 明暗主题与防闪烁

主题偏好存 localStorage,但**读取必须内联在 `<head>` 的同步脚本里**——如果等外部 JS 加载再设置 `data-theme`,深色模式用户会先看到一帧白屏。CSS 用自定义属性组织两套令牌:
css :root { --bg: #f6f7fb; --text: #1c2333; ... } [data-theme="dark"] { --bg: #0b0f1c; --text: #e8ecf5; ... }


### SEO 与订阅

构建时一并生成 `sitemap.xml`、`robots.txt`、`feed.xml`(RSS),每页带 Open Graph 与 JSON-LD 结构化数据(Person / BlogPosting / CreativeWork)。博客页内嵌一份 JSON 数据,前端实现无请求的标题/摘要/标签搜索。

## 四、部署:一台只装了 nginx 的服务器

构建产物 `dist/` 是完全静态的文件,部署只需两步:scp 到服务器 `/var/www/personal-site/`,写一段 nginx 配置。没有 Node 运行时、没有 PM2、没有数据库:
nginx server { listen 80 default_server; root /var/www/personal-site; index index.html; location / { try_files $uri $uri/ $uri.html =404; } location ~* \.(css|js|svg)$ { expires 7d; add_header Cache-Control "public, immutable"; } } ```

try_files $uri $uri/ $uri.html 一行同时支持目录式 URL(/projects/xxx/)和静态回退,404 也有自定义页面。

五、小结

这个网站本身就是一个嵌入式工程师的作品:零依赖、零运行时、构建一秒、部署一条命令。就像好的固件——上线之后你几乎感觉不到它的存在,但它一直在稳定运行。

后续计划:接入 Gitee 仓库自动同步 README、给博客加目录导航(TOC)。如果也对静态站点生成感兴趣,欢迎邮件交流。