本文是动手前的[菜谱 + 排错手册]。每条改造都给出改哪个文件、加什么代码、注意什么。
一、任务式改造速查
1. 改站点基础信息
| 需求 | 文件 | 字段 |
|---|---|---|
| 站点标题 | src/site.config.ts | title(同时是头部 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. 改首页列表每页数量
// src/site.config.ts
postsPerPage: 10, // → 改成 5 / 20 …
⚠️ 会让 /{n}/ 分页路由数量变化,需重新构建。
4. 加导航菜单项
src/components/SiteHeader.astro的NAV_DEFS注册表加一行(path → 词条 key + 图标);- 三个
src/i18n/*.json的nav块加词条; src/site.config.ts的nav数组加入对应 path(位置即显示顺序);- 新建对应页面(见 07-页面与路由.md#6)。
导航高亮自动生效,无需额外代码。
5. 把首页名片换成自定义 Banner
src/components/views/HomePage.astro:
---
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 不渲染封面。实现步骤:
src/content.config.ts的 schema 加cover: z.string().optional();scripts/format-post-meta.js的ORDER数组里加上'cover'(否则它会被归入「其他未列出的自定义字段」,排在published之后、updated/draft之前);src/components/PostCard.astro里读取post.data.cover并渲染(注意相对路径要用resolveContentImage,或直接用/content-images/…的产物路径);- 在
src/styles/PostCard.css里加封面样式。
7. 关闭音乐播放器 / 左下角宠物
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. 关闭评论区 / 统计
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 里生成链接:
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. 加带分页的新列表页
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-expandFAB、脚本里的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:
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、其余 lazy | rehypeImages.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 style | main.css(名片、TOC)、PostCard.css | 布局重算范围变大 |
requestIdleCallback 延迟 PhotoSwipe | PostPage.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 数字?
统计脚本只在生产环境注入:
{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 里已被归一化(只取第一项、去空白)。检查:
- frontmatter 的
category是否写了数组(第二项会被丢弃); - 分类名是否含首尾空格或
/; draft: true的文章在生产构建里不参与分组。
Q7. 中文 slug 生成得不好(多音字 / 太长)?
自动生成用的是 pinyin-pro(无声调)。直接手写 slug 覆盖即可:
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() 包一层:
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 未使用。可在自定义页面组件里:
const { getRealtimeStats } = await import('@/stats/visitor-stats')
const data = await getRealtimeStats({ period: 'today' })
注意它会被打进浏览器包,且需要配置 STATS_API_KEY。
Q17. 想让文章支持 MDX / 嵌入 Astro 组件?
当前不支持(未来计划里 MDX 标记为待评估)。若要加:
pnpm add @astrojs/mdx;astro.config.mjs加integrations: [mdx(), …];content.config.ts的 loaderpattern改为'**/*.{md,mdx}';- 注意
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.1 | visitor-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 |
| 组件 Props | 02-组件说明.md |
src/pages/ 路由 | 07-页面与路由.md#1 |
scripts/*.js | 06-构建与脚本.md |
| 插件契约 | 08-插件系统.md |
| markdown 插件链 | 05-内容与Markdown.md#4 |