本项目的配置分散在 6 个文件,本文逐一给出参数表。
| 文件 | 定位 | 谁需要改 |
|---|---|---|
src/site.config.ts | 站点唯一配置入口:站点信息、导航数据、社交、友链、插件开关、音乐播放器 | 每个使用者都必须改 |
astro.config.mjs | 框架级:markdown 链、Vite 构建、Swup、sitemap、站点 URL | 改渲染/构建行为时 |
src/content.config.ts | 文章 frontmatter 的 Zod schema | 加自定义 frontmatter 字段时 |
tsconfig.json | TypeScript 与路径别名 | 加别名时 |
biome.json | 代码风格与 lint | 团队风格统一时 |
pagefind.yml | 搜索索引排除选择器 | 不想被搜到的区域 |
1. src/site.config.ts 顶部导出的类型
改配置前先认识这三个类型(源码第 1–22 行):
export interface SocialLink {
platform: string // 平台标识,见#2.9 支持列表
url: string // 链接地址
}
export interface FriendLinkRaw {
url: string // 必填,无协议会自动补 https://
name?: string // 可选,缺省取域名
desc?: string // 可选,缺省取域名
icon?: string // 可选,以 /friends/ 开头的 public 路径
}
export interface MusicTrack {
title: string // 必填,歌曲标题
src: string // 必填,public/ 下的静态路径(以 / 开头)或完整 http(s) URL
cover?: string // 可选,留空时按钮显示音符图标或从音频元数据提取
}
2. siteConfig 字段逐项说明
使用方式
siteConfig是一个export const普通对象,不是defineConfig包装的。它被astro.config.mjs(构建期读取 URL 与默认语言)和大量组件import使用。改动后需要重启 dev server(
astro.config.mjs在启动时读取它)。纯组件消费的字段在 HMR 下可直接生效。
2.1 url — 站点地址
| 项 | 值 |
|---|---|
| 类型 | string |
| 默认值 | 'https://citrusgrid.pages.dev' |
| 必填 | 是(部署前必须改成自己的域名) |
影响范围(改错了会连带出问题,请逐项检查):
| 消费者 | 用途 |
|---|---|
astro.config.mjs → site | Astro 的 canonical 基准、sitemap 生成 |
BaseLayout.astro | <link rel="canonical">、og:url |
src/pages/rss.xml.ts | RSS 的 site 与条目绝对链接 |
SiteFooter.astro | 版权作者名的跳转链接 |
src/lib/md/rehypeExternalLinks.ts | 判断「外链」的基准 host(同 host 不加 target="_blank") |
尾斜杠
src/pages/rss.xml.ts:8确实会site.replace(/\/+$/, '')去尾部斜杠,但 canonical 的new URL(Astro.url.pathname, Astro.site)在src/layouts/BaseLayout.astro:36,依赖标准 URL 解析。建议不要在url末尾加/。
2.2 站点信息三件套
| 字段 | 类型 | 默认值 | 用途 |
|---|---|---|---|
title | string | 'CitrusGrid' | <title> 前缀、头部 Logo 旁文字、OG og:site_name、JSON-LD publisher |
subtitle | string | 'A pure static Astro blog theme' | 首页(无 title prop 时)<title> = {title} - {subtitle} |
description | string | '这是一个基于 Astro 架构和 CitrusGrid主题的轻量级静态博客。' | 默认 meta description、OG/Twitter、JSON-LD、首页名片简介(ProfileCard 的 .bio) |
description身兼两职:既是 SEO 描述,又是首页名片上作者名下方的那行简介。想分开,需在ProfileCard.astro里改用独立字段。
2.3 avatar / logo
| 字段 | 类型 | 默认值 | 用途 |
|---|---|---|---|
avatar | string | '/avatar.webp' | 首页名片头像(64×64 圆形,object-fit: cover) |
logo | string | '/logo.webp' | 头部 Logo(28×28)、<link rel="icon">、OG 图片默认值、JSON-LD publisher logo |
两者都以 / 开头,指向 public/ 下的文件。换图只需替换 public/avatar.webp 与 public/logo.webp,无需改配置。
BaseLayout的<link rel="icon" type="image/webp" href="/logo.webp">是硬编码的/logo.webp,不是siteConfig.logo。若改logo路径,favicon 也要同步改BaseLayout.astro。
2.4 author
| 项 | 值 |
|---|---|
| 类型 | string |
| 默认值 | 'example name' |
用途:页脚版权署名、首页名片用户名、JSON-LD author.name、文章页版权块的 {author}、ProfileCard 头像 alt。
2.5 defaultLang — 默认语言
| 项 | 值 |
|---|---|
| 类型 | 'zh' | 'ja' | 'en' |
| 默认值 | 'zh' |
影响范围:
| 消费者 | 作用 |
|---|---|
src/lib/url.ts 的 defaultLang(src/i18n/ui.ts 转导出) | 全站取词的默认语言 |
astro.config.mjs | 脚注标题(footnoteLabel)取该语言的 post.footnotes |
src/pages/rss.xml.ts | <language>{defaultLang}</language> |
| 所有路由 | 目前所有 src/pages/*.astro 都 const lang = defaultLang,只构建默认语言页面 |
详见 07-页面与路由.md#5。
非法值直接报错
site.config.ts末尾会调用validateSiteConfig(siteConfig)(实现见src/lib/siteConfigGuard.ts):defaultLang不属于zh/ja/en时抛出Error,dev 启动与构建都会立即中断,不再依赖as强转静默通过。提示为英文(示例:`defaultLang` must be one of "zh" / "ja" / "en", received "fr".),源码中每条英文提示上方都紧邻一行中文译注。
2.6 defaultTheme — 默认主题
| 项 | 值 |
|---|---|
| 类型 | 'auto' | 'light' | 'dark' |
| 默认值 | 'auto' |
| 取值 | 行为 |
|---|---|
'auto' | 跟随系统 prefers-color-scheme,且系统切换时实时跟随 |
'light' | 默认亮色,用户手动切换后写入 localStorage.themeMode 覆盖 |
'dark' | 默认暗色,同上 |
渲染到 <html data-default-theme="…">,由 stores/theme.ts 的 defaultMode() 读取。详见 03-主题与样式定制.md#6。
2.7 postsPerPage — 首页每页文章数
| 项 | 值 |
|---|---|
| 类型 | number(源码未做校验,建议正整数) |
| 默认值 | 10 |
消费者:
| 位置 | 逻辑 |
|---|---|
src/pages/index.astro | all.slice(0, perPage),totalPages = Math.ceil(all.length / perPage) || 1 |
src/pages/[page].astro | paginate(posts, { pageSize: siteConfig.postsPerPage }),再 .filter(p => Number(p.params.page) > 1)(第 1 页由 index.astro 负责,避免重复生成) |
改小这个值会新增大量页面
postsPerPage从 10 改成 5,文章数 100 时会从 10 页变成 20 页,构建时间近似线性增长。
2.8 socials — 社交按钮
| 项 | 值 |
|---|---|
| 类型 | SocialLink[] |
| 默认值 | [{ platform: 'github', url: 'https://github.com' }, { platform: 'rss', url: '/rss.xml' }] |
| 隐藏方式 | 设为 [] |
| 支持值 | 图标 | hover 品牌色 |
|---|---|---|
github | GitHub | #24292e |
twitter / x | #1da1f2 | |
linkedin | #0a66c2 | |
youtube | YouTube | #ff0000 |
instagram | #e4405f | |
facebook | #1877f2 | |
devto | Dev.to | #0a0a0a |
medium | 通用链接图标 | #000000 |
rss | RSS | #f26522 |
email / mail | 邮件 | var(--wp-accent) |
website / blog | 网页 | var(--wp-accent) |
| 其他任意字符串 | 通用链接图标 mdi-link-variant | var(--wp-accent) |
示例:
socials: [
{ platform: 'github', url: 'https://github.com/yourname' },
{ platform: 'twitter', url: 'https://twitter.com/yourname' },
{ platform: 'email', url: 'mailto:you@example.com' },
{ platform: 'rss', url: '/rss.xml' }, // 站内路径,不加 target=_blank
{ platform: 'bilibili', url: 'https://space.bilibili.com/xxx' }, // 未内置,用通用图标 + 主题色
] as SocialLink[],
平台名文本:由 platform 首字母大写生成(github → Github),用作按钮的 aria-label / title(按钮为 42×42 纯图标方块、贴在名片正下方)。想改提示文案,需要在 ProfileCard.astro 中改用固定映射或增加 label 字段。
支持列表的
platform匹配是大小写不敏感的(内部toLowerCase())。
2.9 statsScript — 统计插件
| 项 | 值 |
|---|---|
| 类型 | string(对应 src/stats/ 下的文件名,不含 .ts) |
| 默认值 | 'random-visitor' |
| 停用 | 设为 ''(空字符串) |
| 取值 | 效果 |
|---|---|
'' | 完全不注入统计脚本,ProfileCard 与文章页不渲染 PV/UV 挂载点 |
'random-visitor' | 内置的测试用插件:随机生成数字,便于本地调整排版 |
'visitor-stats' | 内置的正式插件,需自行在 Cloudflare Worker 部署统计服务 |
| 自定义名 | 在 src/stats/ 新建同名 .ts 并实现契约 |
关键行为:统计脚本仅在构建产物(生产环境)中注入。BaseLayout.astro 的条件是:
data-stats-script={(siteConfig.statsScript && import.meta.env.PROD ? siteConfig.statsScript : undefined)}
以及
{siteConfig.statsScript && import.meta.env.PROD && <script>import '@/stats/client'</script>}
挂载点的渲染条件:只有 Boolean(siteConfig.statsScript),不带 PROD 判断(ProfileCard.astro:40、PostCard.astro:28、PostPage.astro:40)。因此:
| 环境 | 挂载点(- 占位与「次 / 人」单位) | 客户端脚本 | <html data-stats-script> |
|---|---|---|---|
pnpm dev | ✅ 渲染(只要 statsScript 非空) | ❌ 不注入 | ❌ 不设置 |
pnpm build | ✅ 渲染 | ✅ 注入 | ✅ 设置 |
也就是说 dev 下能看到 PV/UV 的位置,但数字永远是 -,也不会发出任何统计请求。要验证真实数字必须 pnpm build && pnpm preview。只有把 statsScript 留空('')时挂载点才完全不渲染。
默认是测试插件。
'random-visitor'会显示随机数字,上线前请改成正式插件或留空。
详细契约见 08-插件系统.md#3。
2.10 friends — 友链
| 项 | 值 |
|---|---|
| 类型 | FriendLinkRaw[] |
| 默认值 | 一条示例 { url: 'https://example.com', name: 'test', desc: 'a desc', icon: '/friends/logo.webp' } |
| 空数组效果 | 显示空状态插画页(不是隐藏友链入口) |
| 字段 | 必填 | 说明 |
|---|---|---|
url | 是 | 无 http(s):// 前缀会自动补 https:// |
name | 否 | 缺省取域名(去 www.) |
desc | 否 | 缺省取域名 |
icon | 否 | 放 public/friends/ 下,引用写 /friends/文件名.后缀;缺省显示首字母方块 |
示例:
friends: [
{ url: 'https://astro.build', name: 'Astro', desc: 'The web framework for content-driven websites', icon: '/friends/astro.webp' },
{ url: 'example.org', name: '示例站' }, // 自动补 https://,无图标
{ url: 'https://foo.dev' }, // 名称/描述都取域名 foo.dev
] as FriendLinkRaw[],
2.11 commentScript — 评论插件
| 项 | 值 |
|---|---|
| 类型 | string(对应 src/comments/ 下的文件名,不含 .ts) |
| 默认值 | ''(默认关闭评论) |
| 启用内置 utterances | 'utterances' |
启用后还需编辑 src/comments/utterances.ts 的 config.repo 填写 GitHub 仓库,否则适配器会抛错并隐藏整个评论区。
详细契约见 08-插件系统.md#2。
2.12 autoUpdatePostUpdated — 自动写 updated
| 项 | 值 |
|---|---|
| 类型 | boolean |
| 默认值 | false(V1.0.5 起改为关闭) |
| 取值 | 行为 |
|---|---|
true | 每次 buildStart(构建时、dev 启动时)按文章文件 mtime 回写 frontmatter 的 updated 字段 |
false | 完全不执行 updatePostUpdated 插件(astro.config.mjs 里 updatePostUpdated({ enabled: siteConfig.autoUpdatePostUpdated }) 会返回 null,Vite 忽略该插件) |
为什么默认关闭?
该功能会在构建时改写你的 markdown 源文件。若你习惯「先 build 再 commit」,开启后会把时间戳一起提交;若你在 CI 上构建,还可能因只读文件系统或权限导致构建失败。建议本地按需开启。
相关注意点(更新说明.md 原文提示):
pnpm format-post-meta(元数据排序)仍是手动执行,建议先排版再构建;- 建议先
pnpm build再git commit,让 slug/updated 的改动一次性提交完。
2.13 musicPlayer — 右下角磁铁音乐播放器
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | boolean | true | 是否渲染整个播放器 |
playlist | MusicTrack[] | [] | 显式播放列表;空数组 = 构建期自动扫描 public/music/ |
playlist 为空时的自动扫描规则:
| 环节 | 行为 |
|---|---|
| 扫描目录 | public/music/(不存在时返回 [],不报错) |
| 识别扩展名 | .flac .mp3 .m4a .ogg .wav .opus .aac .webm |
| 排序 | localeCompare(a, b, 'zh'),即按中文文件名排序 |
title | 去扩展名的文件名 |
src | /music/${encodeURIComponent(文件名)}(中文会自动 URL 编码) |
cover | 不设置 → 客户端从音频元数据提取(FLAC PICTURE / MP3 ID3v2 APIC,读文件头 2MB) |
显式指定示例:
musicPlayer: {
enabled: true,
playlist: [
{ title: '预言 Prophecy', src: '/music/预言 Prophecy.flac', cover: '/music/prophecy-cover.webp' },
{ title: '闪亮', src: '/music/闪亮.flac' }, // 封面从元数据自动提取
{ title: '原色', src: '/music/原色.flac' },
] as MusicTrack[],
},
加歌最省事的做法:
直接把音频文件丢进
public/music/,保持playlist: [],重新构建即可。标题就是文件名(所以文件名即显示名)。
2.14 nav — 顶栏导航
| 项 | 值 |
|---|---|
| 类型 | string[](站内路径) |
| 默认值 | ['/archive', '/categories', '/tags', '/series', '/friends', '/about'] |
SiteHeader.astro 按数组顺序渲染顶栏项;首页 / 固定第一位、不可移除,不要写进数组(写了也会被过滤)。数组去重,未在组件 NAV_DEFS 注册表登记的 path 会被忽略(新增自定义页面需先在 NAV_DEFS 登记词条 key 与图标)。空数组 [] 则顶栏只剩首页一项。缺失时回退为空(仅首页)并警告:nav 被删除或写成非字符串数组时,src/lib/siteConfigGuard.ts 的 validateSiteConfig() 会输出英文 console.warn(源码中附中文译注),顶栏按空数组渲染,构建不会中断。详见 02-组件说明.md#31-siteheaderastro。
2.15 pet — 左下角宠物挂件
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
enabled | boolean | true | 是否渲染左下角宠物(视线跟随 / 眨眼 / 休眠 / 滑出)以及名片上的「唤回宠物」按钮 |
pet: {
enabled: true,
},
关闭方式:把 enabled 改为 false,Pet.astro 即不输出任何 DOM(是「不渲染」而非「隐藏」),ProfileCard.astro 里那颗唤回按钮也不会渲染。
开启后多出的交互(细节见 02-组件说明.md#35-petastro):
| 交互 | 说明 |
|---|---|
| 长按 550ms | 弹出「请去休息」气泡(文案取自 i18n 的 pet.dialogLabel / pet.title / pet.desc / pet.confirm),气泡里只有「关闭」一个按钮 |
| 关闭后 | 在 localStorage 写入到期时间戳 petClosedUntil(24 小时),宠物按方格消解退场并整块隐藏;这段时间刷新页面也不会出现 |
| 唤回 | 首页名片社交条上出现「唤回宠物」按钮(词条 pet.restore),点一下即清掉时间戳,宠物以方格聚合重新出现;时间戳自然过期后刷新同样会回来 |
与 nav / musicPlayer 不同,组件读取用的是可选链 siteConfig.pet?.enabled === true,所以整个 pet 字段缺失也不会报错,只是按关闭处理;validatePet() 会在这种「缺失或非布尔」的情况下输出英文 console.warn(提示宠物按关闭处理),不中断构建。字段说明、行为与关闭方法见 02-组件说明.md#35-petastro。
3. astro.config.mjs 参数表
export default defineConfig({
site, // 来自 siteConfig.url
server: { host: '::' }, // dev 监听所有网卡(IPv6 通配)
preview: { host: '::' }, // preview 同上
build: { inlineStylesheets: 'always' }, // 所有 CSS 内联进 HTML
markdown: { ... }, // 见#3.2
integrations: [ swup({...}), sitemap() ], // 见#3.3
vite: { ... }, // 见#3.4
})
3.1 顶层选项
| 选项 | 值 | 说明 |
|---|---|---|
site | siteConfig.url | canonical / sitemap / RSS 基准 |
server.host | '::' | 开发服务器监听所有网卡,便于局域网/手机真机调试 |
preview.host | '::' | 预览服务器同上 |
build.inlineStylesheets | 'always' | 关键 CSS 内联,消除首屏 CSS 请求(代价:HTML 体积增大,多页共享的 CSS 无法复用缓存) |
3.2 markdown 配置
markdown: {
syntaxHighlight: false, // 关闭 Astro 内置 shiki,改用自定义 Prism 构建期高亮
processor: unified({ ... }),
}
| 处理器选项 | 值 | 说明 |
|---|---|---|
gfm | true | GitHub Flavored Markdown(表格、任务列表、删除线、自动链接) |
smartypants | false | 不把直引号转成弯引号 |
remarkRehype.footnoteLabel | 按 defaultLang 取 post.footnotes | 脚注区标题走 i18n(zh「脚注」/ ja / en) |
remarkRehype.clobberPrefix | 'post-' | 脚注 id 前缀,从默认 user-content- 改为 post-(避免被 GitHub 式清洗规则误伤) |
remark 插件链(按顺序):
| 顺序 | 插件 | 作用 |
|---|---|---|
| 1 | remarkBreaks | 单个换行也渲染成 <br>(贴合中文写作习惯) |
| 2 | remarkEmoji | :smile: → 😄 |
| 3 | remarkMath | 解析 $…$ / $$…$$ 数学公式 |
| 4 | remarkContainers(自定义) | 解析 :::info / :::tip / :::warning / :::danger / :::details |
rehype 插件链(按顺序):
| 顺序 | 插件 | 作用 |
|---|---|---|
| 1 | rehypeKatex | 数学公式 → KaTeX HTML |
| 2 | rehypeCodeBlock(自定义) | <pre><code> → 带语言图标、行号、复制按钮的 .code-block(含 Prism 高亮) |
| 3 | rehypeImages(自定义) | 图片包成 .img-frame + a.img-lightbox,注入 LQIP 渐变;首图 loading="eager" fetchpriority="high",其余 lazy |
| 4 | rehypeRelativeLinks(自定义) | ./其他.md → /posts/其他-slug/;相对图片 → /content-images/{hash}.ext |
| 5 | rehypeTableWrap(自定义) | 表格外包 div.table-wrapper(横向滚动容器) |
| 6 | rehypeExternalLinks(自定义) | 站外链接加 target="_blank" rel="noopener noreferrer" |
| 7 | rehypeSlug | 标题加 id(TOC 锚点基础) |
| 8 | rehypeAutolinkHeadings | 标题尾部追加 # 锚点链接(behavior: 'append',class heading-anchor,aria-label="Heading anchor") |
顺序敏感:
rehypeKatex必须在rehypeSlug之前(KaTeX 会生成带 id 的元素);rehypeRelativeLinks必须在rehypeImages之后(前者需要处理后者生成的href)。调整顺序可能破坏功能。
3.3 integrations — Swup 与 Sitemap
Swup 参数:
| 参数 | 值 | 说明 |
|---|---|---|
theme | false | 不使用 Swup 官方主题,过渡样式自己写在 main.css |
animationClass | 'transition-swup-' | 动画类前缀,实际用 .transition-swup-fade |
containers | ['#swup-container'] | 只替换这个容器(改这里要同步 BaseLayout 的结构) |
smoothScrolling | false | 关闭,锚点滚动自行实现(带 80px 偏移):同页目录/标题锚点由 PostPage 处理,跨页锚点由 BaseLayout 接管(见 07-页面与路由.md#84) |
cache | true | 缓存已访问页面 |
preload | { hover: true, visible: true } | 悬停与进入视口时预取 |
accessibility | true | 无障碍播报 |
progress | true | 顶部进度条(样式见 main.css 的 .swup-progress-bar) |
updateHead | true | 换页时更新 <head>(title / meta) |
updateBodyClass | false | 不替换 body class(避免主题类丢失) |
globalInstance | true | 挂到 window.swup(SearchModal 与 BaseLayout 依赖此全局实例) |
ignore | 函数 | 见下方规则 |
ignore 回调规则:
ignore: (url) => {
if (url.includes('#')) return true // 含 # 的一律跳过 Swup:同页锚点由 PostPage、跨页锚点由 BaseLayout 点击委托接管,都不会原生闪现
const path = new URL(url, 'https://example.com').pathname
if (/\.(xml|txt|json|pdf|zip|png|jpe?g|webp|gif|svg|ico|avif|woff2?|ttf|eot)$/i.test(path)) return true
if (/^\/(pagefind|fonts|images)\//.test(path)) return true
return false
}
Sitemap:sitemap() 无参数,生成 /sitemap-index.xml 与 /sitemap-0.xml。SiteFooter 的 Sitemap 链接硬编码为 /sitemap-index.xml。
3.4 vite 配置
Vite 插件(构建期数据生成):
| 插件 | 来源 | 作用 | 可调参数 |
|---|---|---|---|
tailwindcss() | @tailwindcss/vite | Tailwind v4 编译 | — |
ensurePostSlugs() | scripts/ensure-post-slugs.js | 补全 slug,写 post-index.json | 无 |
generateLqips() | scripts/generate-lqips.js | 图片主色采样 | 环境变量 LQIP_FORCE=1 强制全量重算 |
updatePostUpdated({ enabled }) | scripts/update-post-updated.js | 回写 updated | siteConfig.autoUpdatePostUpdated |
optimizeDeps.include:
[
'photoswipe', 'photoswipe/lightbox', 'astro/virtual-modules/transitions-*.js',
// Swup 的 Astro 客户端模块:按需拆成 8 个入口,全部预打包
'@swup/astro/idle', '@swup/astro/serialise',
'@swup/astro/client/Swup', '@swup/astro/client/SwupA11yPlugin',
'@swup/astro/client/SwupHeadPlugin', '@swup/astro/client/SwupProgressPlugin',
'@swup/astro/client/SwupPreloadPlugin', '@swup/astro/client/SwupScriptsPlugin',
]
预打包这些依赖,避免 dev 首次访问文章页(PhotoSwipe)或首次触发换页(Swup 各插件)时出现「新依赖发现 → 重新预构建 → 页面刷新」。Swup 那 8 项是逐插件列出的,@swup/astro 升级后若入口文件名变化需同步这一份清单。
build 参数:
| 参数 | 值 | 说明 |
|---|---|---|
minify | 'esbuild' | JS 压缩器 |
esbuildOptions.minify | true | — |
esbuildOptions.drop | ['debugger'] | 移除 debugger 语句 |
esbuildOptions.pure | ['console.log', 'console.debug'] | 标记为无副作用,可被摇树移除 |
cssCodeSplit | true | CSS 按 chunk 拆分 |
cssMinify | 'lightningcss' | 用 lightningcss 压缩(比 esbuild 更激进) |
assetsInlineLimit | 4096 | <4KB 的资源内联为 base64 |
pure: ['console.log']的副作用:如果你在客户端脚本里用
console.log调试,生产构建会被移除(console.warn/console.error保留)。调试请用pnpm dev。
4. src/content.config.ts — 文章 Schema
const posts = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/posts' }),
schema: z.object({ ... }),
})
export const collections = { posts }
| frontmatter 字段 | Zod 类型 | 默认值 | 归一化 / 校验 |
|---|---|---|---|
title | z.string().min(1) | — | 至少 1 字符 |
slug | z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/).nullish() | — | 可空;允许小写字母数字与单个连字符分隔 |
index | z.number().int().min(0).default(0) | 0 | 非负整数,>0 视为置顶 |
description | z.string().min(1) | — | 至少 1 字符 |
category | z.union([z.string(), z.array(z.string())]).default('未分类') | '未分类' | transform 后只取归一化列表的第 1 项 |
tags | z.union([z.string(), z.array(z.string())]).default('') | '' | transform 后变成去空、去重(大小写不敏感)的数组 |
published | z.coerce.date() | — | 强制转 Date;字符串 YYYY-MM-DD HH:mm:ss 可被解析 |
updated | z.coerce.date().optional() | — | 可选 |
draft | z.boolean().default(false) | false | 生产构建时 draft: true 的文章被过滤 |
normalizeList 的行为:(源码第 5–18 行)
// 输入 "a, b, a" 或 ['a','b','a'] → 输出 ['a','b']
// 规则:按逗号拆分(字符串时)→ trim → 跳过空值 → 按 toLowerCase() 去重 → 保留原始大小写
所以 tags: [Markdown, markdown] 只会保留第一个 Markdown。
加自定义字段的示例:(例如加一个 cover 封面图字段):
// src/content.config.ts
schema: z.object({
// ...原有字段
cover: z.string().optional(),
}),
然后 Post 类型会自动带上 post.data.cover。别忘了同步 scripts/format-post-meta.js 的 ORDER / LAST 数组,否则 pnpm format-post-meta 会把它排到末尾。
5. tsconfig.json
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist", "node_modules"],
"compilerOptions": {
"baseUrl": ".",
"resolveJsonModule": true,
"paths": { "@/*": ["src/*"] }
}
}
| 项 | 说明 |
|---|---|
extends | Astro 的 strict 预设(比 strictest 宽松,允许 any 推断但不允许隐式 any) |
include | 覆盖全项目(含 scripts/)与 Astro 生成的类型 |
paths | @/* → src/*,全项目统一用 @/ 别名 |
resolveJsonModule | 允许 import zhJson from './zh.json'(i18n/ui.ts 与 astro.config.mjs 依赖) |
i18n key 的类型安全(T13)
src/i18n/ui.ts从zh.json递归派生出点路径联合类型MessageKey(深度上限 5 层,当前语言包最深 3 层),t(key: MessageKey, params?)因此能在pnpm check/pnpm type-check阶段拦住拼错的键(例如t('nav.hom')直接编译报错)。- 为让 TS 推导出字面量键,
src/env.d.ts不再declare module '*.json'(那条声明会把 JSON import 统一成Record<string, any>);全项目只有i18n/ui.tsimport JSON。- 取词未命中时:dev 环境在服务端日志打印
[i18n] missing message: <key> (<lang>.json),生产环境保持静默回退(显示 key 原文),行为与之前一致(构建产物里 dev 告警分支已被消除)。- 三份语言包键集合一致性由
pnpm check-i18n(scripts/check-i18n.js)手动校验,不一致时exit 1并列出缺失 / 多出的键。
加新别名:
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"]
}
astro.config.mjs不读取tsconfig.json的 paths(它是被 Vite 处理的),所以在astro.config.mjs里import必须用带扩展名的相对路径,例如./src/site.config.ts、./src/lib/md/rehypeCodeBlock.ts(源码确实这么写)。
6. biome.json
| 配置块 | 值 | 说明 |
|---|---|---|
vcs.enabled | false | 不读取 gitignore |
files.ignoreUnknown | false | 未知文件类型报错 |
formatter.indentStyle | "space" | 空格缩进 |
formatter.indentWidth | 2 | 2 空格 |
formatter.lineWidth | 100 | 100 列换行 |
linter.rules.preset | "recommended" | 推荐规则集 |
javascript.formatter.quoteStyle | "single" | 单引号 |
javascript.formatter.semicolons | "asNeeded" | 无多余分号 |
css.parser.tailwindDirectives | true | 必须,否则 Biome 无法解析 @theme / @custom-variant / @import "tailwindcss" |
overrides[**/*.astro].linter.rules.correctness | noUnusedVariables: off,noUnusedImports: off | Astro 组件里常有「模板用不到但类型需要的导入」,故关闭 |
命令对应:
| 命令 | 实际执行 | 范围 |
|---|---|---|
pnpm format | biome format --write ./src | 只格式化 src |
pnpm lint | biome check --write ./src | 检查 + 修复 src |
pnpm format:all | biome check --write ./src ./scripts ./astro.config.mjs ./biome.json ./package.json ./tsconfig.json | 全项目 |
7. pagefind.yml
exclude_selectors:
- ".katex" # KaTeX 渲染出的公式排版节点
- ".katex-display" # 块级公式
- ".code-header" # 代码块头部(语言标签 + 复制按钮,不应进索引)
- ".md-toc" # markdown 内的目录块
7.1 搜索索引的范围控制
| 机制 | 位置 | 作用 |
|---|---|---|
data-pagefind-body | PostPage.astro 的 .post-content | 只有该容器内的内容被索引(否则 Pagefind 会索引整个页面,包括导航与页脚) |
data-pagefind-filter="tag" | 文章页的标签链接 | 建立 tag 过滤器(当前 UI 未使用,可用于高级筛选) |
exclude_selectors | pagefind.yml | 从索引中剔除指定选择器 |
常见扩展示例:
exclude_selectors:
- ".katex"
- ".katex-display"
- ".code-header"
- ".md-toc"
- ".img-frame" # 图片
- ".md-container" # 提示容器整块
加了新选择器要重新构建:
pagefind.yml只在postbuild.js里被读取(pagefind --site dist会读项目根的pagefind.yml),改动后必须pnpm build。
8. 配置文件改动影响速查
| 改了 | 需要重启 dev? | 需要重新 build? | 还需要连带改什么 |
|---|---|---|---|
siteConfig.title / subtitle / description | 否(组件 HMR) | 是(部署时) | — |
siteConfig.url | 否 | 是 | RSS、sitemap、canonical 全部重算;astro.config.mjs 的 site 也在启动时读取,改后请重启 dev |
siteConfig.avatar / logo | 否 | 是 | favicon 是硬编码 /logo.webp,换路径要同步 BaseLayout.astro |
siteConfig.defaultLang | 是 | 是 | 检查 src/i18n/*.json 是否有对应词条 |
siteConfig.defaultTheme | 否 | 是 | — |
siteConfig.postsPerPage | 否 | 是 | 分页路由数量变化 |
siteConfig.nav | 否 | 是 | 新增自定义页面需同步 SiteHeader.astro 的 NAV_DEFS 与 i18n nav.* 词条;缺失 / 非字符串数组时回退为仅「首页」并输出英文 console.warn(lib/siteConfigGuard.ts) |
siteConfig.statsScript / commentScript | 否 | 是 | 对应插件文件需存在并完成配置 |
siteConfig.autoUpdatePostUpdated | 是 | 是 | 会改写 md 源文件 |
siteConfig.musicPlayer | 否 | 是 | 自动扫描依赖 public/music/ 内容 |
siteConfig.pet | 否 | 是 | 仅决定是否输出宠物 DOM(含名片上的唤回按钮);整个字段缺失不会报错(可选链读取),但会输出英文 console.warn |
astro.config.mjs 任意项 | 是 | 是 | — |
content.config.ts schema | 是 | 是 | 已有文章可能校验失败,注意 .astro/ 类型缓存 |
main.css 变量 | 否(HMR) | 是 | — |
pagefind.yml | — | 是 | — |