AstroBlog 是一个以 Astro 静态生成为核心的个人站点。Markdown/MDX Content Collections 是公开内容源,Astro 页面和组件负责生成 HTML,Svelte 负责后台和少量有状态交互;评论、访客、音乐、AI 和 GitHub 写入则由 Cloudflare Worker 提供运行时能力。站点的关键不是某个框架,而是明确区分构建时数据、浏览器状态和边缘服务。
本文是项目的代码级维护入口。需要修改功能时,应先确定它属于内容、页面、样式、浏览器脚本、后台、Worker 还是构建脚本,再进入对应目录。不要从最终生成的 dist/ 反推源代码,也不要把运行时 API 逻辑塞进 Astro 页面。
运行环境和依赖边界
AstroBlog 当前使用 Node.js 24.18.0 和 pnpm 9.14.4。核心依赖包括 Astro 6.4.6、@astrojs/mdx 6.0.3、@astrojs/svelte 8.1.2、Svelte 5.56.4、Swup 4.9.2、Tailwind CSS 4.3.2、Three.js 0.184、Expressive Code 0.43.1、KaTeX 0.16、Pagefind 1.5.2 和 Sharp 0.34。
依赖按职责分为几层:Astro 和 Content Collections 负责静态页面与内容;Svelte 负责后台表单和管理状态;Swup 负责主站页面切换;Tailwind、分层 CSS、Iconify 和字体包负责视觉;remark/rehype、Expressive Code、KaTeX、Mermaid 负责 Markdown 处理;Pagefind 负责构建后的站内搜索;Cloudflare Worker、D1 和 R2 负责需要运行时状态的服务。
目录结构
src/├─ components/ 公共、页面、功能和后台组件├─ config/ 站点、导航、侧栏、页面和管理员配置├─ content/ 文章、动态、书籍、友链等内容文件├─ layouts/ Layout、MainGridLayout、AdminLayout├─ pages/ Astro 文件路由和 API 静态端点├─ plugins/ Markdown、图片、Mermaid 和构建插件├─ scripts/ 浏览器端追踪和页面交互脚本├─ styles/ reset、tokens、base、components、pages、admin├─ utils/ 内容查询、URL、图片、主题和运行时工具└─ workers/ 主站 GitHub 写入代理workers/├─ comments/ 评论、访客、音乐 Worker 与 D1/R2└─ ai/ AI Worker 与独立 D1scripts/ 构建、数据同步、验证和生成工具public/ 原路径输出的静态资源和 BlogRoom 快照源文件和生成文件要分开看。src/constants/icons.ts、字体 CSS 和 public/room-data/*.json 可以由构建脚本重新生成,不应该手工修改;src/content/、src/config/、src/pages/、src/components/ 和 Worker 源码是实际修改入口。
Astro 配置和 Markdown 管线
astro.config.mjs 设置静态输出、站点 URL、尾斜杠、旧路径重定向、Svelte/MDX/Swup/Icon/Sitemap 集成和 Vite 构建选项。Markdown processor 使用 unified:remark 阶段处理数学公式、阅读时间、摘要、指令、分节和 Mermaid;rehype 阶段处理 KaTeX、提示框、标题 slug、图注、外链、邮箱保护、GitHub 卡片和标题锚点。
Expressive Code 负责代码框、行号、折叠区域和复制按钮,主题由 [data-theme] 选择器切换。插件顺序有依赖,例如 rehypeRaw 影响后续节点插件,图片和 Mermaid 也需要在正文进入组件前完成转换。新增 Markdown 语法时,应该在 processor 中加入插件并补测试,不要在单个文章组件里用正则解析全文。
全局布局
src/layouts/Layout.astro 生成完整 HTML、head 元数据、字体和首屏初始化脚本。它读取站点标题、语言、主题色、壁纸模式和页面路径,把初始主题、Banner 高度和首页视频域名注入内联脚本。页面可见后,setting-utils.ts 接管主题、壁纸和 localStorage 同步。
src/layouts/MainGridLayout.astro 组合 Navbar、Banner、左右侧栏、主内容和 Footer,并根据 sidebarLayoutConfig、页面类型和响应式断点生成网格类。文章页可以临时显示对侧栏,隐藏侧栏或允许内容溢出都由布局 props 决定。AdminLayout.astro 是独立骨架,不加入主站 Swup 容器。
首页根路由和内容模块在 src/pages/[...page].astro,首页 Hero 位于 src/components/layout/HomeHero.astro,导航配置在 src/config/navBarConfig.ts,侧栏顺序和显示条件在 src/config/sidebarConfig.ts。修改首页模块顺序不要直接移动布局层的 DOM,先确认该模块属于页面内容还是公共外壳。
文章内容系统
src/content.config.ts 当前定义 13 个集合。文章集合通过 glob loader 读取 src/content/posts/ 下的 .md 和 .mdx,排除 _fixtures;Schema 使用 astro/zod 校验 title、published、draft、description、tags、category、layout、image、pinned、comment、order 等字段。
src/content/posts/*.md(x) ↓ loader + ZodCollectionEntry<"posts"> ↓content-utils.ts ↓文章列表 / 详情 / 归档 / 分类 / 标签 / 搜索getPublicPosts() 排除 draft: true 和 fixture;getSortedPosts() 按置顶、日期和同日 order 排序,并补充上一篇/下一篇;getSortedPostsList() 删除正文,只给列表页面提供 metadata。文章列表路由是 src/pages/posts/[...page].astro,卡片组件是 src/components/layout/PostCard.astro,详情路由是 src/pages/posts/[...slug].astro。
文章详情通过 render(entry) 输出正文组件和 headings,再交给 ArticleHero.astro、ArticleContent.astro、目录、评论和 SEO 组件。文章图片在 Markdown 阶段由 src/plugins/article-images/ 解析相对路径,不能在浏览器运行时拼接源文件路径。
文章列表为空时的排查顺序是:文件扩展名 → Frontmatter → draft → _fixtures → collection loader → getPublicPosts() → 构建使用的内容版本 → 部署页面。不要先改分页组件。
前端样式、主题和 Swup
全局 CSS 入口是 src/styles/main.css,只负责导入和层顺序。当前层顺序为 theme, reset, tokens, base, components, utilities, overrides;颜色、字体、间距、圆角和 z-index 等语义值放在 src/styles/tokens/。公共组件样式、页面样式和后台样式分开,组件应消费 var(--card-bg) 等语义变量而不是重复十六进制值。
Swup 配置替换主内容、Banner 和动态侧栏容器,后台、音乐页和独立页面被忽略。主布局脚本需要区分点击、content:replace 和 page:view:点击阶段只处理反馈,替换后同步路径和布局,页面可见后初始化目录、评论、Mermaid 和页面专用脚本。需要反复执行的监听器使用 AbortController 或 cleanup,避免页面切换后重复绑定。
修改样式运行 pnpm verify:styles;修改主题、壁纸或 Swup 还要运行浏览器回归检查。样式层级正确不代表移动端、键盘焦点和页面后退行为正确。
管理后台和 GitHub 写入
后台页面集中在 src/pages/admin/,文章功能主要由 src/components/admin/posts/PostManager.svelte 和 src/components/edit/WriteEditor.svelte 提供。AdminLoginGate.svelte 处理管理员会话和 X-Admin-Token,Manager 负责列表、表单、预览、上传和状态。
文章内容由 src/utils/adminContent.ts 局部修改 Frontmatter,尽量保留字段顺序、注释、换行和正文。发布链路为:
WriteEditor.svelte → adminContent.ts → AdminLoginGate / X-Admin-Token → /api/github → src/workers/github-proxy.js → GitHub Contents / Git Data API → Cloudflare 构建 → Content Collections 生成页面github-proxy.js 是写入安全边界。它检查来源、管理员授权、请求体大小、允许的仓库和分支、路径白名单、HTTP 方法和生产分支保护;批量内容操作还会验证分支 HEAD、文件 blob SHA、文件数量、单文件大小和总 payload 大小。远程文件发生变化时返回 409,后台必须刷新预览,不能覆盖新版本。
新增可写资源时要同步 Manager 数据模型、adminContent.ts、代理白名单、方法限制和 verify-admin 脚本。后台发布返回成功只代表仓库写入成功,公开页面还要等待 Cloudflare 完成新的静态构建。
评论、访客和音乐 Worker
workers/comments/ 同时处理评论、访客和音乐接口,绑定 D1 waline-comments 和音乐 R2。评论表通过 migration 管理,回复用 parent_id 建树;公开接口按审核状态过滤,管理员接口要求 token 或 owner cookie。评论图片使用 multipart,Worker 检查 WebP 文件头、声明长度、尺寸、数量和大小后才写入。
访客代码在 workers/comments/src/visitors.ts。visitor_profiles、visitor_visits 和 visitor_visit_pages 分别表示浏览器归并身份、一次访问和页面事件;设备 ID 和 visit token 用哈希验证,IP 相关字段用 AES-GCM 加密,清理任务按 VISITOR_RETENTION_DAYS 删除过期数据,单次访问最多 256 个 pageview。src/scripts/visitorTracker.ts 采用空闲加载,Swup 导航后批量上报 pageview。
音乐目录和音频对象也在 comments Worker 中处理,D1 保存目录元数据,R2 保存音频、封面和歌词。修改音乐字段时要同时检查 music handler、migration、管理端和播放器状态。
AI Worker
workers/ai/ 是独立 Worker,绑定 D1 yukihime-ai。index.ts 负责编排路由、管理员上下文、凭据、知识检索、用量和审计,providers.ts 负责供应商请求和错误映射。API key 以主密钥加密保存,管理端只看到标签、模型、状态和末四位。
scripts/sync-ai-index/index.mjs 将文章、导航等内容生成摘要、正文、路径、发布时间和内容哈希,写入 ai_documents。查询只取可见匹配文档,不把整个仓库直接塞进提示词。管理员 proposal 模式要求返回受限 JSON,执行前仍需重新验证 GitHub HEAD、目标 blob、路径白名单和精确 before 文本;AI 只能提出修改建议,不能绕过 GitHub Proxy 自动发布。
BlogRoom 数据生成
AstroBlog 构建时运行 scripts/generate-room-data/index.mjs,从 src/content/bangumi/ 和 src/content/friends/ 生成 public/room-data/books.json 与 friends.json。BlogRoom 构建前通过 scripts/sync-room-data.mjs 获取线上数据,失败时依次回退本地 AstroBlog 快照和已有快照,并验证 version、数组和字段。
修改书籍或友链内容时,源文件在 AstroBlog;生成 JSON 是构建产物;BlogRoom 的 src/config/room-data/ 是同步后的快照。三个位置的字段必须保持兼容,不能直接手工改 BlogRoom 快照来“修复”线上内容。
构建流程
AstroBlog 的 pnpm build 顺序大致为:
verify:secrets → verify:styles → verify:comments / runtime-utils / home-wallpaper → generate-room-data → prepare-build → generate-font-css / generate-icons → astro build → reconcile generated assets → pagefind --site dist → normalize generated fonts当前完整构建已生成 89 个页面,Pagefind 索引 64 个页面。构建输出中的空可选集合提示不是失败;真正失败通常来自 Schema、Markdown 插件、图片、环境变量或生成脚本。构建成功也不代表远程 Worker、D1、R2、GitHub 权限或浏览器交互已经通过端到端验收。
修改矩阵
| 想修改的内容 | 首先查看 | 必须同步检查 |
|---|---|---|
| 首页模块 | src/pages/[...page].astro、HomeHero.astro | siteConfig、布局、首页视频和移动端 |
| 页面外壳 | src/layouts/Layout.astro、MainGridLayout.astro | Swup、主题、侧栏和 SEO |
| 导航/侧栏 | navBarConfig.ts、sidebarConfig.ts | 对应组件、响应式和页面类型 |
| 文章字段 | content.config.ts | Frontmatter、查询层、后台编辑器 |
| 文章列表 | content-utils.ts、PostPage.astro | 排序、分页、瀑布流和空状态 |
| 文章详情 | [...slug].astro、components/pages/posts/ | 图片、目录、评论、SEO |
| 颜色和字体 | src/styles/tokens/ | 浅色/深色、后台、样式契约 |
| GitHub 写入 | github-proxy.js | 白名单、认证、分支保护、验证脚本 |
| 评论/访客 | workers/comments/ | migration、前端 tracker、管理面板 |
| AI | workers/ai/ | provider、D1、索引、proposal、测试 |
| BlogRoom 数据 | generate-room-data | BlogRoom 同步脚本、类型和快照 |
本地启动、检查和排错
# 安装依赖并启动 Astro 开发服务器。corepack pnpm installcorepack pnpm dev# 检查 Astro 模板、Content Collections 和样式契约。corepack pnpm check# 验证后台格式、草稿、认证和 GitHub 写入规则。corepack pnpm verify:admin# 运行评论、访客和图片相关 Worker 测试。corepack pnpm verify:comments# 验证 AI provider、索引和提案流程。corepack pnpm verify:ai# 执行完整静态构建、资源生成和 Pagefind 索引。corepack pnpm build文章或 Schema 修改先运行 check;后台修改运行 verify:admin;评论、访客或图片修改运行 verify:comments;AI 修改运行 verify:ai;涉及构建、资源或路由时运行完整 build。远程 Worker 可以使用 comments:dev、ai:dev 和 Wrangler migration 命令单独启动。
排错时先看错误发生在哪一层:内容未生成看 collection 和 Frontmatter;页面结构错误看 route/layout/component;主题和过渡错误看 tokens、Layout 和 cleanup;请求失败看浏览器 Network、src/api 和 Worker 日志;后台发布失败看代理白名单、认证和分支 HEAD;线上页面旧看 Cloudflare 构建 commit、dist/build-version.json 和部署日志。
截至当前工作树,src/content/posts/ 共有 18 个 Markdown/MDX 文件,全部为公开文章,其中 3 篇系统测试文章位于时间线最早位置;_fixtures 中的回归样本不进入公开集合。源码、构建产物和远程服务各有独立生命周期,维护时必须从源文件开始确认,而不是只看浏览器最终页面。