本文档持续记录 AstroPaper 主题的各种自定义配置位置和修改方法。
⚡ 通用修改步骤
- 定位对应的配置文件
- 找到需要修改的代码或配置项
- 进行修改并保存文件
- 开发服务器会自动热重载,实时预览效果
提示:修改后记得在明暗两种主题下都进行测试,确保视觉效果一致。
🎨 背景颜色修改
📁 主要文件位置
颜色变量定义
浅色主题(默认)
:root,
html[data-theme="light"] {
--background: #fdfdfd; /* ← 修改此处 */
}
深色主题
html[data-theme="dark"] {
--background: #212737; /* ← 修改此处 */
}
🔗 导航菜单激活状态样式
📁 样式定义位置
推荐样式配置
.active-nav {
@apply text-accent underline decoration-solid decoration-1 underline-offset-8;
}
样式参数说明
text-accent:文字变为强调色(自动适配明暗主题)- 浅色主题:蓝色
#006cac - 深色主题:橙色
#ff6b01
- 浅色主题:蓝色
decoration-solid:直线样式(替代原来的波浪线)decoration-1:1px 粗细的细线underline-offset-8:8px 间距,提供良好的视觉呼吸感
应用方式
当导航菜单项处于激活状态时,会自动应用 .active-nav 类,显示文字颜色变化 + 细直线下划线效果。
自定义调整建议
如果需要微调下划线间距,可以修改 underline-offset 值:
underline-offset-3:紧凑型间距underline-offset-5:中等间距underline-offset-8:宽松间距(当前推荐)
📱 Social Links 配置(首页)
📁 配置文件位置
配置对象
export const SOCIALS: Social[] = [
{
name: "GitHub",
href: "https://github.com/MaizeDev/my-blog-maize",
linkTitle: `${SITE.title} on GitHub`,
icon: IconGitHub,
},
{
name: "X",
href: "https://x.com/username",
linkTitle: `${SITE.title} on X`,
icon: IconBrandX,
},
// ... 其他社交链接
] as const;
可用图标
IconMail、IconGitHub、IconBrandX、IconLinkedinIconWhatsapp、IconFacebook、IconTelegram、IconPinterest
修改方法
- 添加链接:在
SOCIALS数组中添加新对象 - 删除链接:从数组中移除对应对象或注释掉
- 修改链接:更改
href和linkTitle的值 - 更换图标:使用不同的图标组件(需确保已导入)
显示逻辑
首页会自动检测 SOCIALS.length > 0,如果数组为空则不显示 “Social Links:” 区域。
🌐 分享链接新窗口打开
📁 问题描述
默认情况下,文章页面的分享链接会在当前窗口跳转到分享网站,影响用户浏览体验。
📁 配置文件位置
src/components/ShareLinks.astro
🔧 解决方案
修改 LinkButton 组件,添加 target="_blank" 和 rel="noopener noreferrer" 属性:
<LinkButton
href={`${social.href + URL}`}
target="_blank"
rel="noopener noreferrer"
class="scale-90 p-2 hover:rotate-6 sm:p-1"
title={social.linkTitle}
>
<social.icon class="inline-block size-6 scale-125 fill-transparent stroke-current stroke-2 opacity-90 group-hover:fill-transparent sm:scale-110" />
<span class="sr-only">{social.linkTitle}</span>
</LinkButton>
📝 属性说明
target="_blank":在新窗口/标签页中打开链接rel="noopener":防止新页面通过window.opener控制原页面,提升安全性rel="noreferrer":防止发送 Referer 头,保护用户隐私
💡 效果
修改后,所有分享链接(Facebook、X/Twitter、WhatsApp、Telegram、Pinterest、邮件等)都会在新窗口中打开,不会中断用户当前的博客浏览体验。
☁️ Cloudflare Pages 构建问题排查与解决
📁 问题背景
在使用 Obsidian + Templater 插件创建博客文章模板后,提交到仓库并通过 Cloudflare Pages 构建时遇到两个关键错误,导致构建失败。
❌ 错误一:InvalidContentEntryDataError(日期格式错误)
🔍 错误日志
[InvalidContentEntryDataError] blog → slug data does not match collection schema.
pubDatetime: Expected type "date", received "string"
modDatetime: Expected type "date", received "string"
Location: /opt/buildhome/repo/src/data/blog/_templates/blog-post-template.md:0:0
🧠 根本原因
- 模板文件被误处理:Astro 内容集合系统尝试解析
_templates/目录下的模板文件 - 占位符格式问题:模板中的
{{date}}T{{time}}Z是字符串占位符,不是有效的日期对象 - Schema 验证失败:内容集合 schema 要求
pubDatetime和modDatetime必须是z.date()类型
🔧 解决方案
更新内容集合配置,明确排除模板目录:
📁 文件位置:src/content.config.ts
const blog = defineCollection({
loader: glob({
pattern: ["**/[^_]*.md", "!_templates/**"], // ← 添加排除规则
base: `./${BLOG_PATH}`
}),
// ... 其他配置
});
💡 最佳实践建议
- 模板目录命名:始终使用
_开头的目录名(如_templates)存放模板文件 - 显式排除:在 glob 模式中明确添加排除规则
!_templates/** - Obsidian Templater 配置:确保日期输出为 ISO 8601 格式:
pubDatetime: <%* tp.date.now("YYYY-MM-DDTHH:mm:ss") + "Z" %> ```
❌ 错误二:ImageNotFound(图片路径错误)
🔍 错误日志
[ImageNotFound] Could not find requested image `../../assets/images/forrest-gump-quote.png`
Location: /opt/buildhome/repo/src/data/blog/_releases/how-to-update-dependencies.md
🧠 根本原因
相对路径层级计算错误:
- 文件位置:
src/data/blog/_releases/how-to-update-dependencies.md - 错误路径:
../../assets/images/forrest-gump-quote.png(只回退了2级) - 正确路径:
../../../assets/images/forrest-gump-quote.png(需要回退3级)
🔧 解决方案
修正相对路径层级:
📁 文件位置:src/data/blog/_releases/how-to-update-dependencies.md
# 修正前(错误)
ogImage: ../../assets/images/forrest-gump-quote.png
# 修正后(正确)
ogImage: ../../../assets/images/forrest-gump-quote.png
💡 路径计算规则
| 文件所在目录 | 到项目根目录的层级 | ogImage 路径前缀 |
|---|---|---|
src/data/blog/ | 3级 | ../../../ |
src/data/blog/_releases/ | 4级 | ../../../../ |
src/data/blog/_examples/ | 4级 | ../../../../ |
🚀 更优解决方案建议
考虑使用 Astro 的 @/ 别名路径(如果配置支持):
ogImage: @/assets/images/forrest-gump-quote.png
这样可以避免手动计算相对路径层级的复杂性。
🧹 构建产物清理规范
🔧 清理命令
# 删除构建产物
rm -rf dist
# 删除搜索索引
rm -rf public/pagefind
📋 清理时机
- 提交代码前
- 重新构建前
- 部署失败后排查问题时
📁 版本控制
确保 .gitignore 中包含以下条目:
dist/
public/pagefind/
.astro/
✅ 验证步骤
- 本地构建测试:运行
pnpm run build确保无错误 - 清理构建产物:删除
dist/和public/pagefind/ - 提交代码:推送修复后的代码到仓库
- 云端验证:观察 Cloudflare Pages 构建日志确认成功
📝 Notes 微博客系统
这次已经在 AstroPaper 里接入了独立的 Notes 系统,用来承载短文本、碎碎念和带图片的轻内容,不再和长文章混在一起。
📁 关键文件
src/content.config.tssrc/data/notes/src/pages/notes/index.astrosrc/components/NoteItem.astrosrc/utils/getSortedNotes.tssrc/utils/noteFilter.ts
✅ 当前能力
- Notes 内容单独放在
src/data/notes/ - 使用独立 content collection,不需要
title和description - 完整支持 Markdown 和图片渲染
- 按
pubDatetime倒序排列 - 沿用发布过滤逻辑,支持
draft - 页面路由为
/notes
Content Schema
const notes = defineCollection({
loader: glob({
pattern: ["**/[^_]*.md", "!_templates/**"],
base: "./src/data/notes",
}),
schema: () =>
z.object({
pubDatetime: z.date(),
modDatetime: z.date().optional().nullable(),
draft: z.boolean().optional(),
timezone: z.string().optional(),
}),
});
推荐 Frontmatter
---
pubDatetime: 2026-03-30T21:10:00Z
---
天气不错,适合散步。今天折腾了 AstroPaper,感觉很棒。
页面表现
/notes页面保留了两种展示方式:Cards和Lines- 每条 Note 的时间固定显示在右下角
- 首屏先显示一部分内容,继续滚动时逐步加载
- 没有分页,使用
IntersectionObserver做渐进加载 - 卡片和列表都保留了轻量动画,并兼容
prefers-reduced-motion
排序与过滤逻辑
const getSortedNotes = (notes: CollectionEntry<"notes">[]) => {
return notes
.filter(noteFilter)
.sort(
(a, b) =>
Math.floor(new Date(b.data.pubDatetime).getTime() / 1000) -
Math.floor(new Date(a.data.pubDatetime).getTime() / 1000)
);
};
🔎 Search 页面重构
搜索页已经从”只有壳子”改成了真正可用的本地全文搜索,并且样式收敛到更接近 AstroPaper 的简洁风格。
📁 关键文件
src/pages/search.astrosrc/env.d.tspublic/pagefind/
✅ 当前能力
- 搜索路由为
/search - 搜索框支持即时搜索和回车搜索
- 查询词会同步到 URL 参数
?q= - 搜索结果会区分
Posts、Notes、Page - 搜索结果展示路径、标题、摘要和命中片段
- 空状态、加载状态、错误状态都已处理
实现方式
搜索页直接调用 Pagefind 浏览器端索引:
const importPagefind = new Function(
"return import('/pagefind/pagefind.js');"
) as () => Promise<PagefindModule>;
然后在前端执行:
pagefind.search(term):回车立即搜索pagefind.debouncedSearch(term, {}, 180):输入时防抖搜索
开发时注意
Pagefind 依赖构建后的索引,所以本地如果第一次测试搜索,需要先运行一次构建命令:
pnpm run build
🧱 全局布局与显示逻辑整理
这次还顺手把页面的公共结构做了统一,不再由每个页面单独插入 Header 和 Footer。
📁 关键文件
src/layouts/Layout.astrosrc/layouts/Main.astrosrc/layouts/AboutLayout.astrosrc/layouts/PostDetails.astrosrc/components/Header.astrosrc/components/Footer.astro
✅ 当前策略
Layout.astro统一负责全局Header、内容区和Footer- 主页面默认使用固定 Header + 固定 Footer
- 文章详情页显式关闭固定 Footer,保留自然阅读流
Main.astro统一了标题、描述和正文之间的间距- 已移除页面顶部的面包屑导航,例如
Home » Search
Header 调整
- Header 变为吸顶导航
- 移动端菜单使用统一的展开/关闭逻辑
- 当前导航项会自动高亮
- 搜索和明暗模式按钮继续保留在导航区
Footer 调整
- 主页面 Footer 固定在视口底部
- 为避免遮挡内容,布局层会自动给内容区预留底部空间
- 文章详情页通过
fixedFooter: false关闭固定 Footer
布局开关示例
const layoutProps = {
title: `${title} | ${SITE.title}`,
author,
description,
pubDatetime,
modDatetime,
canonicalURL,
ogImage,
scrollSmooth: true,
fixedFooter: false,
};
📚 当前页面规则总结
- 首页、Posts、Tags、Archives、Notes、Search、About、404:统一使用全局 Header 和 Footer
- Posts 列表分页保留不变
- Tags 分页保留不变
- Notes 不分页,改为滚动渐进加载
- 文章详情页保持阅读优先,不启用固定 Footer
✍️ 文章页排版修正
最近对文章详情页又做了一轮排版回收,重点是把阅读体验调顺,而不是单纯把字放大。
📁 关键文件
src/layouts/PostDetails.astrosrc/styles/typography.css
这次修复了什么
- 文章页重新对齐到主布局宽度,不再出现比 Header 更宽导致的视觉偏移
- 文章标题去掉了会干扰混合中英文标题的平衡换行策略
- 标题下方的日期和编辑链接缩小了一档,并收紧了上边距
- 正文与
h2 / h3 / h4的字号、行高、间距重新做了比例调整 - 去掉了技术文章里不太合适的
h3斜体效果
长标题换行 bug 的原因
长标题显示异常,主要不是单纯”字体太大”,而是两个因素叠加:
- 标题使用了
text-balance,浏览器会尝试把多行标题分配得尽量平均。 - 标题里像
WD-40、3-bit这样的连字符词,浏览器会把-当作可断行点。
这两个规则叠加后,就会出现把一个词拆成两半的情况,比如:
WD-
40
解决方案
现在文章页会在渲染标题时,把字母或数字之间的普通连字符 - 转成不可断行连字符 ‑,避免这类词被拆开:
const displayTitle = title.replace(
/([A-Za-z0-9])-([A-Za-z0-9])/g,
"$1‑$2"
);
这样像 WD-40、3-bit 会整体换行,不会再被硬拆开。
📝 经验总结
- 内容集合隔离:模板文件、归档文件必须与实际博客文章物理隔离
- 路径精确计算:相对路径必须严格计算目录层级深度
- 本地先行验证:重要修改先在本地构建验证,再提交到云端
- 错误日志分析:仔细阅读构建日志,定位具体文件和行号
📁 内容集合配置优化(排除主题自带文档)
📌 问题背景
_examples 和 _releases 目录包含 AstroPaper 主题自带的示例文章和发布说明文档。这些文件:
- 不需要在博客中展示
- 可能包含不符合当前 schema 的字段
- 会增加不必要的构建处理时间
🔧 解决方案
更新内容集合 glob 模式,明确排除所有主题自带目录:
📁 文件位置:src/content.config.ts
const blog = defineCollection({
loader: glob({
pattern: [
"**/[^_]*.md", // 匹配所有不以 _ 开头的 Markdown 文件
"!_templates/**", // 排除模板目录
"!_examples/**", // 排除示例文章目录
"!_releases/**" // 排除发布说明目录
],
base: `./${BLOG_PATH}`
}),
// ... 其他配置
});
💡 优势说明
- 构建性能提升:减少不必要的文件处理
- 错误预防:避免主题自带文档的 schema 不兼容问题
- 内容纯净:确保博客只显示用户原创内容
- 维护简便:无需手动删除或隐藏主题自带文件
📋 目录用途说明
| 目录 | 用途 | 是否排除 | 原因 |
|---|---|---|---|
_templates | Obsidian 博客模板 | ✅ 是 | 防止模板占位符导致构建错误 |
_examples | 主题示例文章 | ✅ 是 | 主题自带,无需展示 |
_releases | 主题发布说明 | ✅ 是 | 主题自带文档,无需展示 |
| (根目录) | 用户原创文章 | ❌ 否 | 实际博客内容 |
🚀 最佳实践建议
- 保留但排除:不要删除主题自带文件,便于参考和更新
- 统一管理:所有非用户内容都使用
_前缀并统一排除 - 定期检查:主题更新后检查是否有新的归档目录需要排除
💬 评论系统迁移(Giscus)
📁 关键文件
src/constants.tssrc/components/Comments.tsxsrc/config.ts
迁移时需要注意的点
Giscus 不是只改仓库地址就能恢复。它依赖四个关键配置:
reporepoIdcategorycategoryId
如果你删除旧仓库、重建新仓库,那么旧的 repoId 和 categoryId 会直接失效。
Giscus 正常工作的前提
- 仓库必须是 公开仓库
- 仓库必须启用 GitHub Discussions
- 必须安装 Giscus App
当前代码里的处理方式
为了避免配置未完成时前端直接显示报错,评论组件现在会先检查配置是否完整:
export const GISCUS_IS_CONFIGURED = Boolean(
GISCUS.repo && GISCUS.repoId && GISCUS.category && GISCUS.categoryId
);
如果配置不完整,就不渲染 Giscus,而是显示一张提示卡片,引导去重新生成配置。
新仓库迁移后要改的地方
src/constants.ts里的reposrc/constants.ts里的repoIdsrc/constants.ts里的categoryIdsrc/config.ts里的SITE.editPost.url
可替代的评论系统
- Giscus:最适合 GitHub 驱动博客,支持明暗主题,和 AstroPaper 兼容度高
- Utterances:更轻,基于 GitHub Issues,接入简单,但表现比 Giscus 更朴素
- Cusdis:界面干净,支持暗色模式和自托管,不依赖 GitHub Discussions
- Waline:功能最丰富,可玩性高,但需要服务端和数据库,维护成本更高