本文是动手前的[菜谱 + 排错手册]。每条改造都给出改哪个文件、加什么代码、注意什么。

一、任务式改造速查

1. 改站点基础信息

需求文件字段
站点标题src/site.config.tstitle(同时是头部 Logo 旁文字、og:site_name)
站点副标题(首页 <title> 后半段)同上subtitle
站点描述(SEO + 首页名片简介)同上description
作者名同上author
头像 / Logo替换 public/avatar.webp、public/logo.webp;路径不同时改 avatar / logo—
站点域名同上url(不带尾斜杠)

换 logo 路径时,BaseLayout.astro 里 favicon 是硬编码 /logo.webp,需同步改。

2. 换主色 / 暗色配色

见 03-主题与样式定制.md#2(6 步,含 --wp-grid-image 与 AboutPage 硬编码色这两个易漏点)。

3. 改首页列表每页数量

ts
12
// src/site.config.ts
postsPerPage: 10,   // → 改成 5 / 20 …

⚠️ 会让 /{n}/ 分页路由数量变化,需重新构建。

4. 加导航菜单项

  1. src/components/SiteHeader.astro 的 NAV_DEFS 注册表加一行(path → 词条 key + 图标);
  2. 三个 src/i18n/*.json 的 nav 块加词条;
  3. src/site.config.ts 的 nav 数组加入对应 path(位置即显示顺序);
  4. 新建对应页面(见 07-页面与路由.md#6)。

导航高亮自动生效,无需额外代码。

5. 把首页名片换成自定义 Banner

src/components/views/HomePage.astro:

astro
12345678910
---
import PostCard from '@/components/PostCard.astro'
import Pagination from '@/components/Pagination.astro'
// 移掉 ProfileCard 的 import
---
<!-- 原:<ProfileCard lang={lang} /> -->
<section class="mx-auto mb-8 max-w-[800px] rounded-[12px] bg-surface p-6 shadow-card">
  <h1 class="font-serif text-3xl font-bold text-ink">{siteConfig.title}</h1>
  <p class="mt-2 text-ink-2">{siteConfig.description}</p>
</section>

6. 让文章列表显示封面图

当前 PostCard 不渲染封面。实现步骤:

  1. src/content.config.ts 的 schema 加 cover: z.string().optional();
  2. scripts/format-post-meta.js 的 ORDER 数组里加上 'cover'(否则它会被归入「其他未列出的自定义字段」,排在 published 之后、updated/draft 之前);
  3. src/components/PostCard.astro 里读取 post.data.cover 并渲染(注意相对路径要用 resolveContentImage,或直接用 /content-images/… 的产物路径);
  4. 在 src/styles/PostCard.css 里加封面样式。

7. 关闭音乐播放器 / 左下角宠物

ts
12345678
musicPlayer: {
  enabled: false,
  playlist: [],
},

pet: {
  enabled: false,
},

两者都是「不渲染」而非「隐藏」:musicPlayer.enabled 为 false 时 MusicPlayer.astro 不输出 DOM,pet.enabled 不为 true 时 Pet.astro 同样什么都不输出(连 lib/client/pet.ts 的初始化也会因查不到 [data-pet] 直接返回),名片社交条上那颗「唤回宠物」按钮也不再渲染。

如果只是想让宠物安静一阵子,不必改配置:长按宠物 550ms 会在头顶弹出「请去休息」气泡,点「关闭」即写入 petClosedUntil(当前时间 +24 小时)并整块隐藏;这期间首页名片上会出现「唤回宠物」按钮,点它就当场复活。想立刻放它出来又不想点按钮,清除浏览器 localStorage 里的 petClosedUntil 再刷新即可。

8. 只保留部分导航项

从 src/site.config.ts 的 nav 数组里删掉对应 path 即可(首页 / 固定第一项、不可移除);data-nav-path="__fallback__" 兜底项仍会处理「无匹配」情况。

9. 关闭评论区 / 统计

ts
12
commentScript: '',
statsScript: '',

留空后连挂载点都不会渲染(不是隐藏,是不输出 DOM)。

10. 去掉页脚「Powered by」

src/components/SiteFooter.astro 第 32–33 行的 <p> 直接删除。

11. 修改文章版权协议

版权文案在 src/i18n/*.json 的 post.copyright 块:

key作用
licenseText含 {link} 占位,会被替换成协议链接(PostPage.astro 用 split('{link}') 拆分前后两段)
licenseName链接文字
author含 {year} 占位
mayTitle / mayText / mustTitle / mustText「您可以 / 但必须遵守」清单
disclaimer免责声明

协议本身(CC BY-NC-SA 4.0)在 PostPage.astro 里生成链接:

ts
1
const licenseDeedUrl = `https://creativecommons.org/licenses/by-nc-sa/4.0/deed.${lang}`

换协议需要改这一行 + 徽章文本(<span>CC BY-NC-SA 4.0</span> 是硬编码)。

12. 加自定义图标

见 02-组件说明.md#4A 或仓库内的 src/content/posts/添加自定义图标.md,三步:粘贴 path → 注册到 icons 映射 → 用 <Icon d={…} /> 渲染。

13. 加带分页的新列表页

见 07-页面与路由.md#6.2。

14. 屏蔽搜索结果里的某些内容

编辑 pagefind.yml 的 exclude_selectors,然后重新构建(pagefind 只在 postbuild.js 里跑)。

14.5 关闭文章阅读动效(滚动淡入) / 目录收起按钮

  • 阅读动效:删掉 src/styles/PostPage.css 末尾的 @media (prefers-reduced-motion: no-preference) 块(.post-content.reading-reveal 两条规则),并从 PostPage.astro 的 scheduleIdle 回调里移除 initReadingReveal() 及其函数定义。
  • 目录收起按钮:删掉 PostPage.astro 中 <aside id="toc-affix"> 内的 #toc-collapse 按钮与 #toc-expand FAB、脚本里的 positionTocExpandFab / setTocCollapsed / tocCollapsed 与 initPage 中的两行 addEventListener 和 setTocCollapsed(tocCollapsed)、PostPage.css 的 .toc-collapse-btn / .toc-expand-fab / .toc-affix 过渡规则,以及 src/i18n/*.json 的 post.collapseToc。

15. 让搜索支持按标签筛选

PostPage.astro 的标签链接已带 data-pagefind-filter="tag",索引里已有该过滤器。需要在 SearchModal.astro 里调用 Pagefind 的 filter API:

ts
1
const search = await pf.search(q, { filters: { tag: selectedTag } })

(当前 UI 未提供筛选控件,属预留能力。)

二、性能优化机制清单(改代码时别无意破坏)

机制实现位置破坏了会怎样
零字体网络请求main.css 用系统 sans-serif / monospace引入 web 字体后首屏会增加一个阻塞请求
构建期代码高亮rehypeCodeBlock.ts(Prism 在 Node 侧跑)若改成客户端高亮,会引入 ~30KB JS
图片 LQIP 主色占位generate-lqips.js + rehypeImages.ts去掉后首屏图片区域是灰白 shimmer
首图 eager、其余 lazyrehypeImages.ts 的 firstImgDone 状态全设 eager 会拖慢首屏
内联脚本压缩postbuild.js每个 HTML 多几百字节
modulepreload 注入postbuild.js 的 injectPreloads()动态 chunk 变成瀑布式请求
Swup 预取(hover/visible)astro.config.mjs 的 preload换页变慢,但能减少无效请求
评论懒加载(提前 300px)comments/client.ts 的 IntersectionObserver首屏就会加载第三方评论脚本
统计缓存(sessionStorage + TTL)stats/client.ts每次换页都请求统计服务,易触发限流
音乐播放器在 Swup 容器外BaseLayout.astro切页会中断播放
contain: layout stylemain.css(名片、TOC)、PostCard.css布局重算范围变大
requestIdleCallback 延迟 PhotoSwipePostPage.astro 的 scheduleIdle()首屏就要加载 PhotoSwipe

不要[优化]掉 audioCover.ts 的 cache: 'no-store'

源码注释已说明原因(与 <audio> 媒体栈缓存冲突会触发 Chromium 的 net::ERR_CACHE_OPERATION_NOT_SUPPORTED)。

三、FAQ

Q1. 为什么 pnpm dev 下搜不到任何文章?

Pagefind 索引由 scripts/postbuild.js 在 pnpm build 时生成到 dist/pagefind/。dev 环境没有索引,搜索框会显示 search.failed(「搜索索引不可用(需构建后可用)」)。

验证方式:pnpm build && pnpm preview。

Q2. 为什么 dev 下看不到 PV/UV 数字?

统计脚本只在生产环境注入:

astro
1
{siteConfig.statsScript && import.meta.env.PROD && <script>import '@/stats/client'</script>}

同时也是因为 data-stats-script 属性在 dev 下为 undefined。用 pnpm build && pnpm preview 验证。

Q3. 构建后 git status 显示文章被改动了?

三个脚本会写源文件:

脚本改什么
ensure-post-slugs补 slug 字段
update-post-updated写 updated(仅当 autoUpdatePostUpdated: true)
format-post-meta重排字段顺序(手动执行)

预期行为。建议改完文章后 pnpm build 再 git commit。

Q4. 相对链接构建时报警告怎么办?

警告原因解决
relative link escapes src/content/../ 出界改成正确的相对路径
target post not found目标 .md 不存在或没有 slug检查文件名/拼写,或先构建一次生成 slug
links to a draft post which is not built in production链到了 draft: true 的文章发布该文章或去掉链接
unsupported relative link target目标既不是文章也不是图片用绝对路径(/… 或 https://…)

警告不阻断构建,但会导致预取 404。

Q5. 图片为什么不显示 / 显示旧图?

现象原因解决
完全不显示图片不在 src/content/ 下,或用了 /xxx.png 但这种路径实际不存在相对路径引用;/ 开头的必须是 public/ 下真实文件
显示旧图content-images 的 hash 只基于路径,而响应是 immutable 长缓存改文件名,或清理部署平台缓存
dev 下正常,构建后 404图片扩展名不在支持列表(.webp .png .jpg .jpeg .gif .svg .avif .ico .bmp)转成支持格式

Q6. 分类 / 标签页是空的?

categories/[name].astro 的过滤是严格相等(p.data.category === name),而 category 在 schema 里已被归一化(只取第一项、去空白)。检查:

  1. frontmatter 的 category 是否写了数组(第二项会被丢弃);
  2. 分类名是否含首尾空格或 /;
  3. draft: true 的文章在生产构建里不参与分组。

Q7. 中文 slug 生成得不好(多音字 / 太长)?

自动生成用的是 pinyin-pro(无声调)。直接手写 slug 覆盖即可:

yaml
1
slug: my-custom-slug

只要符合 ^[a-z0-9]+(?:-[a-z0-9]+)*$ 即可(小写、数字、单连字符)。

Q8. pnpm install 报错说必须用 pnpm?

package.json 有 "preinstall": "npx only-allow pnpm"。解决:npm i -g pnpm 后改用 pnpm install。

Q9. 加了新的 markdown 插件但没生效?

两种可能:

情况说明
改的是 astro.config.mjs 的 markdown 配置需要重启 dev server(markdown 处理器在启动时构建一次)
只改了页面效果但 RSS 没变页面与 RSS 用两份独立插件链(astro.config.mjs vs src/lib/md/pipeline.ts),要改两处

Q10. Swup 换页后我的脚本不执行了?

必须用 onPageLoad() 包一层:

ts
12
import { onPageLoad } from '@/lib/pageLifecycle'
onPageLoad(initMyFeature)

并且 initMyFeature 内部要做幂等保护(如 el.dataset.bound)。

Q11. 主题切换时页面「闪一下」或者过渡很生硬?

applyTheme() 会给 <html> 加 is-theme-transitioning 类 300ms,用 !important 禁用全站 transition(animation 不禁,避免取消后重播)。若你新增的元素仍有过渡:

  • 检查是否用了内联 style="transition: …"(内联样式同样会被 !important 覆盖,一般没问题);
  • 若用了 CSS @keyframes 且 animation-fill-mode 影响布局,考虑加 prefers-reduced-motion 降级。

Q12. 部署后 canonical / RSS 链接不对?

siteConfig.url 未改成自己的域名。它是 astro.config.mjs 的 site、canonical、OG、RSS、sitemap 的唯一来源。改后需要重启 dev 并重新构建。

Q13. 首屏有「复制」两个字一闪而过?

这是 rehypeCodeBlock.ts 里服务端硬编码的中文按钮文案,客户端 PostPage.astro 会用 i18n 文本覆盖它。非中文站点若在意,请把 rehypeCodeBlock.ts 的复制按钮改为不含文本(只保留图标)。

Q14. 归档页的「置顶」区为什么顺序和预期不同?

ArchivePage.astro 里置顶区保持传入顺序(即 getPublishedPosts() 的 index 降序),不额外排序;非置顶区按 published 降序后按年分组。

Q15. 如何让某篇文章不出现在列表但 URL 可访问?

两种方式:

方式效果
draft: true生产构建完全不生成该页面(dev 可见)
从 getPublishedPosts() 结果里过滤需要改 src/lib/posts.ts,例如按 tags 排除

Q16. 想加「阅读量趋势图」之类的富统计?

visitor-stats.ts 已导出 getRealtimeStats()(走 /stats 接口,需 STATS_API_KEY),但当前 UI 未使用。可在自定义页面组件里:

ts
12
const { getRealtimeStats } = await import('@/stats/visitor-stats')
const data = await getRealtimeStats({ period: 'today' })

注意它会被打进浏览器包,且需要配置 STATS_API_KEY。

Q17. 想让文章支持 MDX / 嵌入 Astro 组件?

当前不支持(未来计划里 MDX 标记为待评估)。若要加:

  1. pnpm add @astrojs/mdx;
  2. astro.config.mjs 加 integrations: [mdx(), …];
  3. content.config.ts 的 loader pattern 改为 '**/*.{md,mdx}';
  4. 注意 remarkContainers、rehypeCodeBlock 等自定义插件对 MDX 的兼容性需要回归测试。

Q18. 想换评论系统但保留 utterances 的配置?

不要删 utterances.ts——直接新建 <新系统>.ts 并把 commentScript 改过去即可。注意:import.meta.glob 会让每个插件文件都产出独立 chunk(即使没被指名),所以删掉不用的插件文件能减小 dist/_astro/ 体积,但不会影响运行时请求(未指名的 chunk 永远不被加载)。

Q19. 点了带 #slug 的跨页链接,为什么不是直接跳到标题?

跨页锚点链接被 BaseLayout 拦截为「SPA 导航到页顶 → 加载后平滑滚动」(与目录点击行为一致,见 07-页面与路由.md#84),这是刻意设计,不再走浏览器原生「闪现」定位:

  • 想恢复旧的原生闪现定位:删掉 BaseLayout.astro 里 <head> 的内联去 hash 脚本,以及底部脚本里的跨页锚点点击委托 + consumePendingScrollHash();
  • 单条链接想走原生整页加载:给 <a> 加 data-no-swup。

同页锚点(正文标题锚点、目录)不受影响,仍由 PostPage 平滑滚动。

Q20. 右下角「回顶」按钮怎么不见了?弹窗关闭也没动画?

1.x 起的交互变化,均为刻意设计:

  • 右侧整合菜单:底部一个常驻「菜单」触发器(data-scroll-menu)负责展开/收起,回顶(data-backtop)等按钮收在触发器左侧横向弹出的弹出层内,常驻显示(无滚动显隐判断);滚动、点击外部或按 Esc 时菜单自动收起;
  • 弹窗关闭增加 0.16s 淡出(wp-closing:wp-fade-out + wp-pop-out),仅搜索弹窗与目录弹窗启用了它;换页(astro:after-swap)和点搜索结果跳转时仍是瞬时关闭,避免与页面切换动画叠加。prefers-reduced-motion 下自动瞬时关闭;
  • 若只改了 main.css 里的动画时长但关闭看起来“卡顿/被截断”,是因为 JS 侧 setTimeout(..., 160) 也是写死值,需同步(见 03-主题与样式定制.md#7.3)。

Q21. 给 <a> 写了 text-* 工具类,颜色却不生效?

V1.0.9 及更早的 main.css 里有一条未分层的裸规则 a { color: var(--wp-link) }。Tailwind v4 的 text-* 等工具类都在 utilities 层,而未分层规则优先级高于任何 @layer,于是锚点上的 text-ink-2 / text-accent 等一律被压掉(表现为「全站所有链接都是链接色」)。

现在该规则已移入 @layer base,工具类恢复正常(提交记录见 main.css)。改样式时注意:

  • 新增全局规则一律放进 @layer(默认 base),否则同样的坑会再次出现;
  • 想让某个锚点不受 base 层影响,用工具类更简单;需要整块覆盖时写成作用域规则(如 .post-content a,它有意不分层);
  • 排查:DevTools 中看计算样式来源,若 a { color: … } 的规则来自「unlayered」并压过工具类,就是它。

附带一提:PostCard 的指针预览克隆(.card-preview-clone)上带有相反主题的 dark / light 类,所以它的链接计算色与当前主题是反的——这是刻意设计,不是这条规则的问题。

四、二次开发自检清单

改配置时

  • siteConfig.url 已改成自己的域名(无尾斜杠)
  • title / subtitle / description / author / avatar / logo 已改
  • description 同时承担「首页名片简介」,确认文案合适
  • statsScript 已从默认的 'random-visitor' 改成正式插件或留空
  • commentScript 若启用,已填 src/comments/utterances.ts 的 repo
  • friends 已替换示例数据(或设为 [])
  • socials 已替换示例数据
  • postsPerPage 符合预期
  • musicPlayer.enabled / playlist 符合预期
  • pet.enabled 符合预期(不需要左下角宠物就设为 false)
  • autoUpdatePostUpdated 在 CI 环境下为 false

改样式时

  • 亮色与暗色都检查过
  • 新增颜色同时定义了 :root 与 .dark
  • --wp-grid-image 的 stroke='%23XXXXXX' 与新主色一致
  • AboutPage.astro 的 16 处 #E86233 已同步(或已改为 text-accent)
  • 移动端断点(768px / 640px / 400px)正常
  • 新增动画补了 prefers-reduced-motion 降级
  • 新增的全局 CSS 规则放进了 @layer(默认 base),没有裸元素规则漏在层外(见 FAQ Q21)
  • 改了头部高度后,同步了 markdown.css 的 scroll-margin-top
  • 改了 <main> 宽度后,同步了 PostPage.css 的 .toc-affix { left }

改结构 / 加组件时

  • 需要跨页保留的 UI 放在 #swup-container 之外
  • 页面脚本用 onPageLoad() 包裹且幂等
  • 用 @/ 别名而不是深层相对路径
  • 文案走 useTranslations(),且三个 i18n JSON 都加了词条
  • 组件 Props 有 TS 类型(interface Props)
  • 新增常驻脚本没有直接 console.log(生产会被 pure 移除)
  • swup.ignore 规则是否需要更新(新资源目录)

加插件时

  • 文件名与配置值一致
  • 只有命名导出
  • 失败用 throw 表达
  • 在 pnpm build && pnpm preview 下验证(dev 下统计不注入)
  • 见 08-插件系统.md#5 的完整清单

发布前

  • pnpm check 通过
  • pnpm type-check 通过
  • pnpm format:all 无改动
  • pnpm build 无警告(相对链接、LQIP 取色)
  • pnpm preview 下检查:首页 / 文章页 / 归档 / 分类 / 标签 / 友链 / 关于 / 404 / RSS / robots.txt / sitemap
  • 搜索可用(/pagefind/ 已生成)
  • 明暗主题切换正常,刷新后保持
  • 移动端(375px 宽)导航菜单与目录弹窗可用
  • robots.txt 与 sitemap 的域名正确
  • 提交前 pnpm build(让 slug / updated 的自动改动一起提交)

五、版本与升级注意

版本关键变更升级时注意
V1.0.1visitor-stats 改用 /total + 缓存;统一路由尾斜杠托管平台 301 规则需与尾斜杠一致
V1.0.2文章相对链接构建期自动转换;派生数据移入 .generated/(不再纳入版本控制)旧的构建脚本路径引用需更新
V1.0.3评论系统插拔化;站点配置从 YAML 改为 TS旧配置必须迁移到 src/site.config.ts,否则报错
V1.0.4新增按文件 mtime 自动更新 updated会改写 md 源文件
V1.0.5分页按钮样式;标题层级色条;details 折叠动画;归档页置顶分区;inject-modulepreloads 与 minify-inline-scripts 合并为 postbuild.js;autoUpdatePostUpdated 默认关闭若你有自定义的 scripts/inject-modulepreloads.js / minify-inline-scripts.js,需迁移到 postbuild.js
V1.0.6音乐播放器组件,默认关闭状态升级到该版本需在 src/site.config.ts 补齐 musicPlayer 配置项(缺失会导致组件解构报错);启用后把音频文件放入 public/music/ 即可在构建时自动发现
V1.0.7新增 Twikoo 评论适配器;主内容区与页脚宽度调整为 800px;主题切换扩散动画;页面切换淡入淡出;分类 / 标签页搜索交互增强若自定义过内容区宽度(main.css、页脚样式),需与 800px 基准对齐;主题切换会以 !important 临时禁用全站 transition,新增元素若自带 transition 需注意(见 FAQ Q11)
V1.0.8右侧滚动控件重构为弹出式整合菜单(回顶收进弹出层、常驻显示、无滚动显隐);音乐播放器面板改为滑入 / 滑出动画;章节级搜索结果展示与跳转;TOC 折叠按钮与阅读滚动动效;全站日期统一按 UTC 处理若自定义过右侧按钮(如 data-backtop)需适配新菜单结构;published 日期改为按 UTC 解释,构建与服务运行时长计算均基于 UTC 时间戳;新增动画在 prefers-reduced-motion 下自动降级
V1.0.9顶部导航重构(图标 + 悬停滑入背景板,移除下划线高亮);site.config.ts 新增 nav 数组自定义导航项;鼠标划过网格背景轨迹特效;个人名片新增数码管时钟与指针预览方窗并移入全局布局(仅首页显示);文章卡片新增指针预览;修复 en/ja 顶栏文字溢出错位与浅色模式系列标识对比度升级需在 src/site.config.ts 补齐 nav 字段(缺失会因 new Set(undefined) 抛错导致构建失败);若自定义过 SiteHeader 结构 / 样式,需适配新的 .nav-clip / .nav-board / .nav-content 结构;轨迹特效与指针预览仅桌面端生效,动画在 prefers-reduced-motion 下自动降级

当前 package.json 版本和 AboutPage.astro 里也硬编码了版本号,发版时两处都要改。

六、文档维护提示

本套文档(posts/all/)由源码逐文件核对生成。改动下列内容时,请同步更新对应文档:

改了更新
site.config.ts 字段04-站点配置.md
main.css 变量03-主题与样式定制.md#3
组件 Props02-组件说明.md
src/pages/ 路由07-页面与路由.md#1
scripts/*.js06-构建与脚本.md
插件契约08-插件系统.md
markdown 插件链05-内容与Markdown.md#4