去年我决定重做个人网站。目标很简单:做一个不像是从模板抄来的网站。

但动手之前得先回答一个问题:用什么工具建这个站?

当时的候选分两类。一类是 React/Next/Nuxt 这种前端框架——我每天都在写的技术;另一类是 Hexo 这类开箱即用的博客框架——很多人的默认选择。最后这两个我都没用,落在一个很多前端同事叫不上名字的东西上:Eleventy。

这篇文章不是什么框架对比评测,也不是"最佳实践"。只是记录一下我为什么这么选。可能有你觉得不对的地方,这很正常——个人网站本来就是个人的。

为什么不用 React/Next/Nuxt

先声明一下:我不讨厌框架。我在公司写 Vue 和 React,不写它们我没法工作。

但个人网站是另一回事。先看看这个项目的实际需求:几十篇 Markdown 文章,套几个模板,变成静态 HTML。交互就三样——主题切换、大纲高亮、一个 canvas 动画。就这么多。

拿 Next.js 来说,它当然能做这件事,SSG 本来就是它的功能之一。但为了用那一小块功能,你得背下整个框架:打包器、路由、hydration、RSC……依赖树越来越大,构建越来越慢,升级还得跟着框架的节奏走。而这一切换来的是什么?Virtual DOM diffing、组件状态管理、SPA 路由——这个项目一个都用不上。个人网站不是应用,它是一本书。框架解决的是"应用"的问题,而我的问题是"怎么让一堆 Markdown 变成有性格的静态页面"——它根本不在框架的射程里。

更麻烦的是,内容会跟框架的抽象纠缠在一起。一旦用了 React/Vue,文章就变成了组件树的一部分,路由、数据流、构建管线全都围绕框架生长。哪天你想推翻重设计,要做的不是改几个模板,而是把内容从框架的壳里一点一点掏出来。对个人网站这种注定会被反复折腾的东西,这是最贵的隐藏成本。

还有运行时这回事。框架网站的访客要下载 JS runtime,页面要等 hydrate 才能交互。我的网站呢?

  • 零运行时依赖:所有页面是纯 HTML
  • 不需要 hydration:加载完就是内容
  • JS 不到 15KB,且全是可选增强(主题切换、光标、canvas)
  • Lighthouse 四项接近全 100(Accessibility 还有些要修)

一个 node_modules 几百 MB 的工程,产出的页面是 15KB 的增强脚本 + 纯 HTML。不是说框架做不出好网站——在团队里我离不开它们。只是场景不对。

为什么不用 Hexo

"不用框架"之后,顺理成章的下一步是博客框架。Hexo、Hugo、VuePress…… 装个主题就能写,省事。

我试过,然后发现一个悖论:它省下来的事,恰好都是我不在乎的事;而我想做的事,全都得跟它搏斗。

Hexo 给你什么?主题系统、插件生态、一套"博客应该长这样"的默认预设。开箱即用,但那个"箱"是别人的——排版、配色、信息架构、交互,所有结构决策都来自主题作者,不来自你。内容是你写的,但内容长什么样,是另一个人的审美决定的。这跟我"不像是从模板抄来的"目标,方向正好相反。

而"自由定制"在博客框架里是个痛苦的过程。想改一个细节,得去翻主题源码;改完一个升级,覆盖全部作废;想加个页面,先查主题支持不支持。配置项写满了,也只是在别人的骨架上化妆。

更根本的问题是,这些框架默认你要写"标准的博客"——文章、分类、标签、归档。但我的网站有 Hero、有技能清单、有项目展示、有工程图纸式的版式。我要的不是"一个博客",是"一个以内容为核心的个人网站",博客只是它的一部分。用博客框架来建它,等于先套上一个你不想要的形状,再想办法把多余的部分敲掉。

繁琐,不自由。两条全踩中了。

11ty:只做最基本的事

Eleventy 的定位用一句话能说完:读 Markdown,套模板,输出 HTML。就这些。

没有主题系统,没有内置样式,没有组件框架,甚至没有默认目录结构。你给它什么它做什么,你不说它不多管闲事。我第一次跑起来的时候有点不适应——它几乎什么"功能"都没有。

但这正是我要的。一个静态网站的难点从来不在生成 HTML——那是最简单的一步。难点在别处:版式怎么定、目录怎么生成、分页怎么做、交互怎么写。而这些,11ty 全都不替你决定,只给你最朴素的四样东西:模板继承、collection、filter、pagination。剩下的一切,都是我的。

  • 我的 CSS 决定网站长什么样——不由任何主题决定
  • 我的 15KB JS 决定交互怎么做——不由任何运行时决定
  • 我的 Nunjucks 模板决定页面结构——不由任何约定决定

在 Next 里,这些是一个接一个的依赖和配置;在 Hexo 里,这些是一个接一个的主题和插件;在 11ty 里,这些是我自己写的代码。个人网站要的就是这个。

构建时能做什么,就绝不留给运行时

11ty 的 filter 系统是我最喜欢的部分。文章右边的目录不是手写的——一个自定义 filter 在构建时解析渲染后的 HTML,提取 h2/h3/h4 生成嵌套 <ul>

eleventyConfig.addFilter("outline", (content) => {
  const headingRegex = /<h([2-4])\s+id="([^"]+)"[^>]*>(.*?)<\/h[2-4]>/g;
  // 解析 → 构建树 → 渲染嵌套 ul
});

中文锚点也是。markdown-it-anchor 默认的 slugify 会丢弃中文字符,标题全变成 #section-1。写一个保留中文的版本:

slugify: (text) => text.trim().toLowerCase()
  .replace(/\s+/g, "-")
  .replace(/[^a-z0-9一-龥-]/g, "");

这样锚点是可读的——#配置驱动-crud-引擎,一眼就知道指向哪里。

再加上 8 篇一页的分页、15 个标签的筛选页,全部在构建时生成。38 篇文章、61 个 HTML 文件,250ms 出完。这三件事,在博客框架里对应"主题的目录组件"、"主题的锚点逻辑"、"主题的分页配置"——每一项想改,都是在跟别人的代码搏斗。在 11ty 里,它们是几个加起来不到 50 行的 filter。

自由之后,网站长成了这样

有了这个自由度,才有后面这些东西。它们没有一个是框架或主题的组件,全是自己写的:

手写 CSS,没有 Tailwind。 先声明一下,我在公司也用 Tailwind,它解决的问题是真实的。但在这里,我的设计系统自然地活在 CSS 变量里:

:root {
  --bg: #0b0d10;
  --accent: #ff5c1a;
  --border: #222a34;
  --font-display: "Anton", "Arial Narrow", sans-serif;
}

改一个 --accent,整个站的色调全变。这种"改一个地方,全局生效"的感觉,在用 Tailwind 的时候很难有——不是它做不到,是你要先花大力气配置它,而配置到最后,你其实就是在写设计系统,只是用它的语法。

工程图纸风格。 冷枪灰配工业安全橙,零圆角,硬边界。两套主题,同一个逻辑,只有一种强调色:

Dark:  #0b0d10 (底) → #12161c (表面) → #222a34 (边界) → #ff5c1a (强调)
Light: #edeff1 (底) → #ffffff (表面) → #c9ced3 (边界) → #e04a0c (强调)

零圆角比听起来麻烦——浏览器在 buttoninputcode、focus ring 上到处偷偷加圆角,每个都要显式覆盖。但全部直角叠起来,"这个网站不一样"的感觉就出来了。

每个 section 开头那行图纸符号(DWG NO. EQ1024-V2 · SHEET 01/06)是信息层次的一部分——访客不用逐行读,扫一眼就知道这是个"文档",有版本号、有状态、有修订记录。

canvas 超立方体。 Hero 那个旋转线框是一个 <canvas> 实时渲染 4D 超立方体的投影——16 个顶点、XY/XZ/XW 三个旋转平面同时转、透视除法,requestAnimationFrame 驱动。不在视口就暂停(IntersectionObserver),开了减少动画就跳过。这是全站唯一一个"奢侈"的视觉元素——不算功能也不算内容,但它是那种"一眼就记住"的东西。

CAD 十字准星。 自定义光标,只在 fine pointer 设备上启用:6×6px 橙色方块 1:1 跟手,30×30px 准星带 lerp 缓动的 trailing follow。直接引用 CAD 软件的十字光标,是"工程图纸"叙事的最后一笔。

主题切换:20 行 JS。 一个按钮、一段脚本、CSS 变量接管一切:

toggle.addEventListener('click', function () {
  const next = html.getAttribute('data-theme') === 'light' ? 'dark' : 'light';
  html.setAttribute('data-theme', next);
  localStorage.setItem('theme', next);
});

在 Hexo 里,上面每一件都是"改主题";在 Next 里,每一件都是"加依赖"。在这里,每一件都是写代码。所有取舍都是自己的,所有代码都是自己的——这是用 11ty 的全部意义。

代价:一点都不省事

说清楚,这套方案不省事。别人用 Hexo 一天上线,我到现在还在纠结 3000 行 CSS 拆不拆;用 Next 有现成的组件生态,而我的 canvas 从投影矩阵开始写起。也没有社区帮你兜底——遇到问题,文档和源码就是你的全部。

但这从来不是效率问题,是所有权问题。省下来的时间,买不来一个长成自己样子的网站。