UES.ONE:构建一个无 CDN、零闪烁的数据驱动型数字花园
在这个信息碎片化的时代,拥有一片属于自己的“数字花园”显得尤为珍贵。
终于,UES.ONE 上线了。它不仅仅是一个博客,更是我对 Web 技术、数据管理与用户体验的一次深度探索。
如果你厌倦了千篇一律的 CMS,或者对目前 Web 页面动辄几兆的 JS 依赖感到疲惫,那么这篇文章或许能给你一些不一样的灵感。在这里,我将带你从零开始,解构这个完全由 数据驱动、零 CDN 依赖 且 极致顺滑 的静态站点是如何构建的。
🌱 起步:为什么是 Hugo?
在技术选型之初,我考察了 WordPress、Hexo、Gatsby 等主流框架。最终,Hugo 凭借其令人发指的构建速度(毫秒级)和 Go 语言的强大生态赢得了我的青睐。
要在 macOS 上开始这一切,只需要一行简单的命令:
brew install hugo为什么选择 Hextra 主题?
有了引擎,还需要一个漂亮的躯壳。我选择了 Hextra。它是一个基于 Next.js 风格文档主题改造而来的 Hugo 主题,不仅支持 Tailwind CSS,还完美融合了“文档”与“博客”两种布局。更重要的是,它的极简主义美学深得我心。
初始化项目并引入主题:
hugo mod init ues.one
hugo mod get github.com/imfing/hextra就这样,骨架搭好了。但要让它拥有灵魂,还差得远。
🏗️ 核心架构:YAML 数据驱动 (Data-Driven Architecture)
传统的博客通常是一篇篇孤立的 Markdown 文章。但我是一个有“整理癖”的人。我希望我的书单、影单、音乐收藏能更有条理,更像是一个关系型数据库,而不是流水账。
因此,我制定了一个 “YAML First” 的数据策略。
在 data/ 目录下,我并没有写死 HTML,而是存储了高度结构化的数据:
books.yaml:阅读记录movies.yaml:观影清单music.yaml:音乐收藏memos.yaml:碎碎念quotes.yaml:精选名言
以 movies.yaml 为例,我是这样存储一部电影的:
- id: "m_001"
title:
zh-cn: "黑客帝国"
en: "The Matrix"
ja: "マトリックス"
year: 1999
rating: 5
poster: "matrix.web"
comment:
zh-cn: "你选蓝药丸还是红药丸?"
en: "Blue pill or Red pill?"有了这些数据,我编写了专门的 Hugo Shortcodes(如 moviewall.html),利用 Hugo 强大的模板引擎将 YAML 渲染成精美的海报墙。
这样做的好处是巨大的:未来如果我想从 Hugo 迁移到 Next.js 甚至移动端 App,我只需要带走这些 YAML 文件,而不需要去几百篇 Markdown 里像考古一样挖掘数据。
⚡ 性能哲学:反其道而行之的“零 CDN”
在 Web 开发过度依赖公共 CDN(如 jsDelivr, unpkg)的今天,我做了一个“反潮流”的决定:全站资源本地化。
你在这个网站上看到的所有 JS 库(Swiper, Fancybox)和 CSS 样式,全部托管在本地的 assets/ 目录下。
为什么这么做?
- 隐私至上:没有 Google Fonts,没有第三方 Analytics 脚本。你的访问记录不会被任何大公司收集进行画像。
- 绝对可控:哪怕公共 CDN 宕机(这并不罕见),或者在弱网环境下,这个网站依然能完美运行。
- 构建优化:利用 Hugo Pipe,我在构建时对这些资源进行 Fingerprint(指纹)处理和 Minify(压缩),由 Cloudflare Pages 进行最终分发,速度丝毫不逊色于 CDN。
🎨 极致体验:告别 FOUC 与 智能预加载
1. 解决 FOUC (Flash of Unstyled Content)
静态网站在实现“暗黑模式”时,最容易遇到的问题就是 FOUC —— 页面加载瞬间,白色背景一闪而过,然后才变成黑色。
为了解决这个问题,我们采用了一种“透明优先,继承为主”的 CSS 策略。所有的卡片组件默认背景色设为 transparent,文字颜色设为 inherit。这意味着,无论浏览器首屏渲染时是亮色还是暗色,组件都会自然地融入背景,无需等待 JS 脚本执行完毕。
2. Memos 的“0 延迟”切换
在 Memos(碎碎念)模块中,我引入了 智能预加载 (Smart Preloading) 逻辑。
当你正在阅读当前的“卡片”时,浏览器已经在后台悄悄下载了“上一张”和“下一张”的图片资源。代码如下:
// 图片预加载优化:静默加载前一张和后一张的图片资源
var nextIdx = (index + 1) % memoData.length;
var prevIdx = (index - 1 + memoData.length) % memoData.length;
if (memoData[nextIdx].image) (new Image()).src = memoData[nextIdx].image;
if (memoData[prevIdx].image) (new Image()).src = memoData[prevIdx].image;这种毫秒级的优化,造就了指尖滑动的极致顺滑感。
3. 一言 (Smart Quotes) 的跨语言同步
首页并没有使用公共的“一言” API,而是使用了我自建的 quotes.yaml 库。
更有趣的是,为了保证多语言切换时的体验一致性,我利用 sessionStorage 实现了状态同步:如果你在中文版首页看到了一句尼采的名言,当你切换到英文版时,你看到的依然是同一句名言的英文版,而不是随机跳到另一句。
这种细节的连贯性,是我对用户体验的执着。
🛠️ 今日重构:代码洁癖的胜利
就在今天,我对全站代码进行了一次大规模重构。
- 配置标准化:将
hugo.yaml从几百行杂乱的配置重组为Core,Modules,Build,Multilingual等七大板块。 - 注释文档化:所有的 Shortcodes 和 Partials 文件,都补全了技术性中文注释。这不仅仅是为了给 AI 看,更是为了给未来的自己看。
- 消灭 Bug:我们甚至为了修复一个
theme-toggle的 HTML 属性值换行导致构建失败的问题,反复推敲了 Go Template 的语法边界。
结语
UES.ONE 是我的自留地,也是我的游乐场。
在这个过程中,我负责构思与审美,AI 负责代码实现与调试。Pair Programming with AI 已经成为我开发的新常态。我们为了一个 div 的高度争论过(比如那个 60vh 还是 70vh 的滚动条问题),也为解决了 z-index 的遮挡而庆祝过。
这是一个还在生长中的花园,代码在变,内容在变,但“用心创造”的初衷不变。
欢迎常来逛逛。