这篇文章是 self-built-space 的一次工程复盘。我把仓库里的设计文档、开发计划、部署文档、运维手册、Obsidian 工作流文档和 Git 提交记录重新读了一遍,按时间线把这个站点从空目录走到当前状态的过程整理出来。
一开始我想做的并不是“再搭一个博客”。更准确地说,我想要一个能长期放东西的自建空间。学习文章放技术笔记和工程实践,点点滴滴放日常记录,资源页放工具、模板、链接和可复制的本地资料,关于页则负责说明我是谁、我在关注什么。
起点:先弄清楚这个站点要解决什么
最早的设计文档里,self-built-space 的定位很明确:它要承载内容,也要承载整理动作。这个判断影响了后面几乎所有技术选择。
我没有从零写一套博客系统,而是选了 Fuwari 做底座。它已经有 Astro 静态站、Markdown、标签分类、归档、RSS、Sitemap、Pagefind 搜索、代码块增强和响应式布局。自己要做的部分集中在另外几件事上:
- 首页的信息结构和视觉识别。
- 学习、点滴、资源三类内容的区分。
- 资源页的独立数据模型。
- 更适合中文阅读的排版。
- 从写作到发布的长期维护流程。
这个选择挺务实。Fuwari 负责那些已经成熟的博客能力,我把精力放在“这个站点为什么存在”和“以后怎么继续写”上。
Sprint 0:把 Fuwari 项目骨架迁进来
第一个提交是 a59d758 chore: init Fuwari project skeleton。这一阶段做的是项目启动。
当时工作区里已经有 docs/ 文档,所以开发计划里没有建议直接在当前目录初始化项目。更稳的做法是先在临时目录生成 Fuwari 项目,确认结构和依赖没问题,再迁移到当前仓库。这样不会把已有设计文档冲掉。
迁移完成后,仓库里有了这些基础能力:
src/pages/路由。src/content/内容集合。- 文章详情页、归档页、RSS、搜索组件。
pnpm dev和pnpm build等命令。- Astro 的静态构建输出。
这个阶段看起来只是“把模板放进来”,但它给后续所有定制提供了一个可以跑、可以构建、可以回滚的起点。没有这个起点,后面的内容模型和视觉调整都会变成一堆散落的实验。
Sprint 1:先把模板改成自己的站点
第二个提交是 ae5b662 chore: sprint 1 - 基础配置。这一阶段处理站点身份。
主要改动包括:
- 站点标题改成 Icicn。
- 副标题改成「学习 · 记录 · 整理」。
- 语言切到
zh_CN。 - 导航改成:首页、学习、点点滴滴、资源、关于我。
- 作者昵称、社交链接和站点地址换成自己的配置。
到这里,站点还没有自己的复杂页面,但已经不是默认模板了。导航名称、语言、作者信息和基础 SEO 都指向同一个内容方向:这是一个中文个人站,而不是 Fuwari 的演示项目。
Sprint 2:先定内容模型
第三个提交是 5515672 feat: sprint 2 - 内容模型。这一步很影响后面的结构。
文章没有被拆成两套 collection。学习文章和点滴文章仍然共用 posts,只是在 frontmatter 里增加了一个 kind 字段:
kind: learning或者:
kind: moments这样做的好处很直接。文章详情页、Markdown 渲染、RSS、搜索、标签、分类、目录和代码块都能继续复用。列表页只需要按 kind 过滤。
资源则单独建了 resources collection。资源卡片和文章不一样,它更像一个索引条目,需要 title、type、url、description、category、tags、featured、updated 这些字段。把它单独建模,比硬塞进文章系统里更干净。
这个阶段也加入了学习、点滴和资源示例数据,方便后面做页面时有真实数据可以跑。
Sprint 3:把核心页面串起来
第四个提交是 41431ec feat: sprint 3 - 页面结构。这一阶段把设计文档里的核心路由落到了代码里。
新增或调整的页面包括:
/:首页,负责个人定位和内容分发。/learning/:只显示kind: learning的文章。/moments/:只显示kind: moments的文章。/resources/:读取资源 collection。/about/:展示个人介绍。
content-utils.ts 也在这个阶段变得更有用。它开始负责按 kind 取文章、按 featured 和更新时间排资源。页面本身不用关心太多内容读取细节。
这一阶段完成后,站点终于有了清楚的信息架构。学习和点滴不再只是导航上的两个词,资源页也不再只是一个未来计划。
Sprint 4:首页视觉和中文阅读体验
第五个大阶段是 ef78326 feat: sprint 4 - landing page visual customization complete。这一步给站点加上了更强的视觉记忆点。
参考 Dala 和 Refero 风格后,首页选择了暗色画布、紫色强调和粒子宇宙的路线。技术上引入了 Three.js,包括:
- 旋涡星系粒子场。
- 鼠标视差和粒子扰动。
- 星空背景和闪烁动画。
- shader 与 Three.js 场景代码。
/landing page 和/home/内容聚合页的双页面结构。
Dala 的原始技术路线很复杂,里面有 GLB 实例粒子、EXR 数据贴图、FBO/GPGPU 模拟和滚动驱动。self-built-space 第一版没有照搬这些。它先用程序生成粒子和 Three.js 场景做出氛围,把实现复杂度压在可维护范围内。
后面几个提交继续调整站点气质:
62bec48精简/home/,让内容区更快进入正文。f4467f7修复 Astro check 报错。25ba7bd打磨关于页。6e440e1调整字体系统和中文排版。8b13e9c增加沉浸式主题。76bfe50增加首页轮播 banner。ceed658改善内容页可读性。
这里我比较喜欢的一点是,首页可以有强视觉,但文章页没有跟着变得浮夸。正文页一直在往可读性靠,字号、行高、卡片边界和移动端布局都比“炫”更优先。
资源系统:从外链列表到本地资源合集
资源页最开始只是外部链接卡片。后来 8afda30 feat: add local resource collections 把资源系统扩了一层。
新增的模型有两个:
resourceCollections:资源合集,比如提示词模板、代码片段。localResources:具体条目,通过collection绑定到某个合集。
页面上也增加了 /resources/<slug>/,用来展示某个本地资源合集。LocalResourceGallery.svelte 负责卡片、弹窗和详情展示,ResourceCopyButton.svelte 负责复制资源主体内容。
这样一来,资源页就不只是“我收藏了一些链接”。它可以放真正能复用的东西,比如代码片段、提示词、配置模板、素材说明。外部资源、本地合集、本地条目各自有自己的模型,后面维护起来也不会混在一起。
搜索、RSS 和项目文档
上线准备阶段主要看两个提交。
e17bcf1 feat: sprint 5 - prepare production deployment 加入了 GitHub Actions 部署 workflow,调整 schema,更新 favicon,也处理了 RSS、资源页、首页和搜索相关细节。
2422ebe feat: refine search architecture and project docs 重构了搜索相关代码,新增 src/lib/search/site-search.ts,同时整理 README,移除一些不再需要的默认项目文档和导航逻辑。
搜索使用 Pagefind。构建命令实际会做两步:
astro build && pagefind --site dist所以搜索不能只靠 pnpm dev 判断。要看真实搜索索引,需要先构建,再用 pnpm preview 检查。这个提醒后来也写进了维护文档。
部署:让服务器只做服务器该做的事
部署文档和运维文档最后确定了发布方式:CI 构建,加 SSH/rsync 发布。
整体流程是:
本地修改内容或代码-> git commit-> git push-> GitHub Actions 安装依赖并构建-> rsync 上传 dist/-> 服务器 release 目录-> current 软链接切换-> Nginx 对外提供静态站点这里有几个取舍我觉得是对的。
第一,Git 仓库是源码、内容和配置的来源。服务器上不直接改文章,也不直接改 dist/。
第二,CI 负责安装依赖、检查、构建和生成搜索索引。服务器不承担构建任务,只托管静态文件。
第三,服务器目录用 releases/ 和 current 软链接。每次部署都是一个独立 release,出问题时可以把 current 指回旧版本。
第四,部署用户是单独的 deploy 用户,不用 root。SSH key、GitHub Secrets、Nginx root、release 清理这些细节都在部署文档里有记录。
GitHub Actions workflow 位于 .github/workflows/deploy.yml。它在 push 到 main 时触发,也支持手动 workflow_dispatch。CI 会依次执行 pnpm check、pnpm type-check、pnpm build,然后用 rsync 上传 dist/,切换服务器上的 current,最后保留最近 5 个 release。
启用后的日常维护
站点启用后,维护手册把日常流程写得很朴素:
pnpm checkpnpm type-checkpnpm buildgit statusgit add <files>git commit -m "<type>: <summary>"git push如果改到了视觉、搜索、首页动效或移动端布局,再额外跑:
pnpm preview内容目录也固定下来:
src/content/posts/ 学习文章和点滴文章src/content/resources/ 外部资源条目src/content/resourceCollections/ 本地资源集合src/content/localResources/ 本地资源内容src/content/spec/about.md 关于页内容这个阶段的重点不是多一个命令,而是形成习惯:本地验证,提交,推送,CI 部署,线上验收。内容更新和代码更新走同一条路径。
Obsidian:把写作入口移到更舒服的地方
最近两个提交把 Obsidian 工作流接了进来:
f372731 feat: add obsidian content workflow0985b65 docs: update obsidian workflow readme
这个方案没有让 Astro 直接读取 Obsidian vault。它加了一个同步脚本,把 Obsidian 当写作源,把网站仓库当发布源。
默认 vault 是:
/Volumes/raw/knowledge/self-built发布目录约定为:
posts/learning/ 学习文章posts/moments/ 点点滴滴文章resources/external/ 外部资源卡片resources/collections/ 本地资源合集resources/local/ 本地资源条目_attachments/ 共享附件templates/ Obsidian 写作模板同步脚本是:
scripts/sync-content-from-obsidian.js常用命令是:
pnpm content:syncpnpm content:publish-check同步关系是:
posts/learning/ -> src/content/posts/<slug>/index.mdposts/moments/ -> src/content/posts/<slug>/index.mdresources/external/ -> src/content/resources/<slug>.mdresources/collections/ -> src/content/resourceCollections/<slug>.mdresources/local/ -> src/content/localResources/<slug>.md脚本还处理了一些 Obsidian 和 Astro 之间的小差异:
- 学习文章自动写入
kind: learning。 - 点滴文章自动写入
kind: moments。 - 通过 frontmatter
slug控制输出路径。 - 支持 Obsidian 图片语法
![[image.png]]。 - 本地附件会复制到网站仓库。
- 远程图片和普通外链保持原样。
这层同步适配很值得保留。直接让网站读 vault 听起来更省事,但 CI、路径、附件、草稿和私有笔记都会变麻烦。现在的方式更清楚:Obsidian 负责写,网站仓库负责发布。
回头看,这条路线学到了什么
第一,内容模型要尽早定下来。kind 字段和资源 collection 很早就出现,所以后面的学习页、点滴页、资源页都有稳定的数据来源。
第二,首页可以做得有记忆点,正文页要收着点。粒子宇宙适合放在入口,长文阅读还是要靠字号、行高、间距和清晰的层级。
第三,部署和内容发布最好走同一条路。这个项目没有单独搞一个“只发文章”的临时流程。文章、资源、配置、页面修改都经过 Git、CI、构建和部署。
第四,资源值得单独建模。链接、合集、可复制条目不是同一种东西。分开之后,资源页才有继续扩展的空间。
第五,Obsidian 工作流真正解决的是写作摩擦。同步脚本把 frontmatter、附件、图片语法和输出目录这些琐碎问题藏起来,写文章时就不用一直切回 src/content/ 里手动对齐格式。
当前状态
到目前为止,self-built-space 已经能支撑一条完整的内容生产链路:
- 用 Astro/Fuwari 承载文章、资源、RSS、Sitemap 和搜索。
- 用自定义页面区分首页、学习、点滴、资源和关于页。
- 用 Three.js 给首页提供视觉识别。
- 用 GitHub Actions 构建并发布到服务器。
- 用 release 目录和
current软链接保留回滚能力。 - 用 Obsidian 写作,再通过
pnpm content:sync同步到网站内容仓库。 - 用
pnpm content:publish-check在本地一次跑完同步、诊断、类型检查和构建。
这个项目现在最有价值的部分,不是某个单独页面,也不是某个视觉效果。它更像一条已经接起来的管线:想法先进 Obsidian,发布内容进入 Astro,Git 记录每次变化,CI 负责构建和部署,服务器只安静地托管静态结果。这个状态不华丽,但很适合长期写下去。