Astro + Fuwari 博客搭建:从主题到个人化改造
目录
本文写于本博客使用 Fuwari 主题的时期,文中的目录结构以当时的 Fuwari 版本为准。
搭个人博客最容易卡住的地方不是「能不能跑起来」,而是跑起来之后不像自己的东西——首页还是模板味,文章卡片没有记忆点,详情页信息太杂,评论和音乐播放器又散落在各种教程里。
这篇按我当前博客的实际结构写:先用 Astro + Fuwari 搭出基础,再逐步接入音乐播放器、Giscus 评论、首页 Hero 改造、文章列表卡片、文章详情页布局和部署检查。目标不是复刻原主题,而是把它改成适合长期写技术笔记的个人站。
一、整体方案
当前博客的核心是:
| 模块 | 选择 | 作用 |
|---|---|---|
| 静态框架 | Astro | 生成静态页面,适合部署到 EdgeOne Pages、Cloudflare Pages、Vercel |
| 主题基础 | Fuwari | 提供文章系统、归档、RSS、Markdown 渲染、目录、明暗主题 |
| 样式体系 | Tailwind CSS + Stylus | 快速写布局,统一主题变量 |
| 页面切换 | Swup | 页面过渡和局部更新 |
| 评论 | Giscus | 使用 GitHub Discussions 做评论区 |
| 音乐 | APlayer + Meting | 底部悬浮播放器 |
| 搜索 | Pagefind | 构建后生成本地搜索索引 |
为什么不从零写主题?
| 方案 | 优点 | 问题 |
|---|---|---|
| 从零写 Astro 博客 | 结构完全可控 | 文章集合、RSS、分页、目录、Markdown 插件都要自己补 |
| 直接用 Fuwari | 功能完整 | 首页和文章页模板味比较重 |
| 在 Fuwari 上改 | 保留成熟功能,同时改视觉 | 需要读懂布局和组件之间的关系 |
这里走第三种:保留 Fuwari 的内容系统,重写关键视觉层。
二、初始化 Astro + Fuwari
如果是新项目,可以直接用 Fuwari 模板初始化;如果已经有仓库,就拉下来安装依赖。
pnpm install
本地启动:
pnpm run dev
构建:
pnpm run build
预览构建结果:
pnpm run preview
当前项目的核心命令在 package.json:
{
"scripts": {
"dev": "astro dev",
"check": "astro check",
"build": "astro build && pagefind --site dist",
"preview": "astro preview",
"new-post": "node scripts/new-post.js",
"format": "biome format --write ./src",
"lint": "biome check --write ./src"
}
}
构建命令后面接了 pagefind --site dist,意思是 Astro 先生成静态站点,再给 dist 目录建立搜索索引。
三、先改全站配置
站点配置按职责拆分在:
src/config/site.ts
src/config/navigation.ts
src/config/integrations.ts
site.ts 管站点标题、语言、主题色、头像和社交链接:
export const siteConfig = {
title: "weidas Blog",
subtitle: "在折腾中生活,在探索中成长",
lang: "zh_CN",
themeColor: {
hue: 105,
fixed: false,
},
};
hue 是主题色相,范围是 0-360。例如:
| 色相 | 大致颜色 |
|---|---|
0 |
红色 |
60 |
黄色 |
105 |
偏绿色 |
200 |
青色 |
240 |
蓝色 |
270 |
紫色 |
330 |
粉色 |
导航在 src/config/navigation.ts:
export const navBarConfig = {
links: [
LinkPreset.Home,
LinkPreset.Archive,
LinkPreset.About,
{
name: "常用工具",
url: "https://webtools.example.com/",
external: true,
icon: "fa6-solid:screwdriver-wrench",
},
],
};
只改导航文字、链接、图标时,优先改 src/config/navigation.ts。只有要改 Dock 外观时,才去动 src/components/Navbar.astro。
四、整理目录结构
日常真正高频修改的是这些位置:
| 路径 | 作用 |
|---|---|
src/content/posts/ |
博客文章 |
src/content/spec/about.md |
关于页 |
src/config/ |
站点、导航和第三方集成配置 |
src/assets/images/avatar.jpeg |
头像 |
src/assets/images/hero-carousel/ |
首页轮播背景 |
src/assets/images/posts/ |
文章封面 |
src/components/HomeHero.astro |
首页 Hero |
src/components/post/PostCard.astro |
首页文章卡片 |
src/pages/posts/[...slug].astro |
文章详情页 |
src/components/integrations/Giscus.astro |
评论系统 |
src/components/integrations/MusicPlayer.astro |
音乐播放器 |
src/layouts/MainGridLayout.astro |
全站布局骨架 |
src/styles/markdown.css |
Markdown 正文样式 |
src/styles/variables.styl |
全站颜色变量 |
生成目录不要手动改:
dist/
.astro/
node_modules/
这些目录会由构建工具生成。手动改了也不会成为真正的源码。
五、写文章和封面
文章放在:
src/content/posts/
可以用脚本新建:
pnpm run new-post my-note --title "我的笔记"
也可以直接写 Markdown。Frontmatter 示例:
---
title: Astro + Fuwari 博客搭建:从主题到个人化改造
published: 2026-03-18
description: 记录从 Astro + Fuwari 初始化个人博客,到接入音乐播放器、Giscus 评论、首页 Hero、文章卡片、详情页布局、部署检查的完整改造流程。
image: ../../assets/images/posts/astro-fuwari-blog-guide.webp
tags: ["Astro", "Fuwari", "博客", "Giscus", "APlayer", "前端"]
category: 博客与写作
---
封面图建议放:
src/assets/images/posts/
从 src/content/posts/<slug>.md 引用时,路径是:
image: ../../assets/images/posts/<slug>.webp
首页文章卡片使用 16:9 比例,推荐封面尺寸:
| 尺寸 | 说明 |
|---|---|
1280x720 |
最低建议 |
1600x900 |
推荐 |
1920x1080 |
更清晰,适合后续复用 |
图片会使用 object-fit: cover 裁切,所以重要内容不要贴边。
六、接入音乐播放器
音乐播放器组件在:
src/components/integrations/MusicPlayer.astro
当前使用 APlayer + Meting:
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css">
<script is:inline src="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script>
<script is:inline src="https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js"></script>
<meting-js
server="netease"
type="playlist"
id="17997617137"
fixed="true"
mini="true"
order="random"
loop="all"
volume="0.7"
lrc-type="1">
</meting-js>
常改参数:
| 参数 | 作用 | 示例 |
|---|---|---|
server |
音乐平台 | netease |
type |
类型 | playlist、song、album |
id |
歌单或歌曲 ID | 17997617137 |
fixed |
固定在页面底部 | true |
mini |
迷你模式 | true |
order |
播放顺序 | random |
loop |
循环方式 | all |
volume |
默认音量 | 0.7 |
组件在全站布局里挂载:
<MusicPlayer />
全站挂载位置在:
src/components/layout/SiteShell.astro
这样所有页面都会显示播放器,不需要每个页面单独引入。
七、接入 Giscus 评论
评论组件在:
src/components/integrations/Giscus.astro
配置集中成一个对象:
const giscusConfig = {
repo: "weidas/Giscus-weidas-blog",
repoId: "你的 Giscus repoId",
category: "Announcements",
categoryId: "你的 Giscus categoryId",
mapping: "pathname",
strict: "0",
reactionsEnabled: "1",
emitMetadata: "0",
inputPosition: "bottom",
theme: "preferred_color_scheme",
lang: "zh-CN",
};
Giscus 的核心是 GitHub Discussions:
| 字段 | 说明 |
|---|---|
repo |
用来存评论的 GitHub 仓库 |
repoId |
仓库 ID |
category |
Discussions 分类名 |
categoryId |
分类 ID |
mapping |
页面和讨论的映射方式 |
theme |
评论区主题 |
lang |
评论区语言 |
文章详情页里直接引入:
import Giscus from "@components/integrations/Giscus.astro";
<Giscus />
当前项目把评论放在正文后面,路径是:
src/pages/posts/[...slug].astro
这样每篇文章都有独立评论区,映射方式用 pathname,URL 不变时评论就不会丢。
八、改首页 Hero
首页 Hero 组件在:
src/components/HomeHero.astro
它做了几件事:
| 功能 | 实现位置 |
|---|---|
| 背景轮播 | getHeroImages() 从统一图片清单读取 hero-carousel |
| 花瓣飘落 | .petal-layer 和 @keyframes petal-fall |
| 圆形头像 | avatarImage |
| 打字机文案 | .typewriter |
| GitHub 链接 | profileConfig.links |
| 个人标签 | personalTags |
轮播图目录:
src/assets/images/hero-carousel/
读取方式:
const imageModules = import.meta.glob<{ default: ImageMetadata }>(
"../../assets/images/hero-carousel/*.{jpg,jpeg,png,webp,avif}",
{
eager: true,
},
);
轮播切换时间:
const slideDuration = 60;
打字机文字:
const emoText = "有些人像黄昏,来时温柔,去时荒凉";
个人标签:
const personalTags = [
{ label: "VPS 驯兽师", href: "/archive/" },
{ label: "逆向爱好者", href: "/archive/" },
{ label: "服务器炼金术士", href: "/archive/" },
{ label: "NixOS 信徒", href: "/archive/" },
{ label: "Root 玩家", href: "/archive/" },
{ label: "终端效率洁癖", href: "/archive/" },
];
这里的设计原则是:首页只负责建立气质,不负责塞满信息。文章列表往下滚就能看到,Hero 区只保留头像、文案、个人入口和几个关键词。
九、改文章卡片
首页文章卡片在:
src/components/post/PostCard.astro
当前卡片结构是:
<article class:list={["note-card", className]} style={style}>
<a href={url} class="note-link" aria-label={title}>
<div class="note-cover-wrap">
<ImageWrapper class="note-cover" />
</div>
<div class="note-body">
<h2>{title}</h2>
<p>{description}</p>
<div class="note-meta-row">
<time>{published}</time>
<span class="word-count">字数</span>
<span class="note-tags">标签</span>
</div>
</div>
</a>
</article>
封面比例由 CSS 控制:
.note-cover-wrap {
width: 100%;
aspect-ratio: 16 / 9;
overflow: hidden;
background: #07080d;
}
.note-cover img {
width: 100%;
height: 100%;
object-fit: cover;
}
卡片只保留这些信息:
| 信息 | 保留原因 |
|---|---|
| 封面 | 建立视觉记忆 |
| 标题 | 文章入口 |
| 摘要 | 帮读者判断内容 |
| 日期 | 判断新旧 |
| 字数 | 判断阅读成本 |
| 标签 | 判断主题 |
分类没有放在卡片里,因为标签已经足够表达主题。分类适合归档统计,不适合每张卡片都展示。
十、改文章详情页
文章详情页在:
src/pages/posts/[...slug].astro
当前顶部只保留:
| 元素 | 说明 |
|---|---|
| 标题 | 页面核心 |
| 头像 | 作者识别 |
| 作者名 | 个人博客标识 |
| 日期 | 发布时间 |
| 标签 | 当前文章主题 |
结构大致是:
<header class="post-header">
<h1>{entry.data.title}</h1>
<div class="post-author-line">
<img src={avatarImage.src} alt={profileConfig.name} />
<span>{profileConfig.name}</span>
<time>{formatDateToYYYYMMDD(entry.data.published)}</time>
<span class="post-tags">
{entry.data.tags.map((tag) => <span class="post-tag">#{tag}</span>)}
</span>
</div>
</header>
<Markdown>
<Content />
</Markdown>
<Giscus />
删掉了这些默认信息:
| 被删内容 | 原因 |
|---|---|
| PV 次数 | 当前没有稳定统计系统,先不放 |
| 文章地址复制块 | 对个人博客正文干扰大 |
| 作者信息大卡片 | 顶部头像已经够了 |
| 许可协议块 | 页脚和站点说明已经覆盖 |
正文宽度由 MainGridLayout.astro 控制:
isPostPage
? "grid grid-cols-1 xl:grid-cols-[1fr_minmax(0,64rem)_16rem] 2xl:grid-cols-[1fr_minmax(0,70rem)_17rem]"
: "grid grid-cols-1"
也就是:
左侧空白 / 中间正文 / 右侧目录
中间正文最大宽度在大屏下是 64rem 到 70rem,比原主题更宽,适合长教程。
十一、保留右侧目录
目录逻辑在:
src/layouts/MainGridLayout.astro
src/components/widget/TOC.astro
只在文章页显示:
{siteConfig.toc.enable && isPostPage && (
<div id="toc-wrapper">
<TOC headings={headings}></TOC>
</div>
)}
开关在 src/config/site.ts:
toc: {
enable: true,
depth: 3,
}
长教程必须保留目录。技术文章动不动几千字,没有目录就很难回到某个小节。
十二、改全站布局
布局核心在:
src/layouts/MainGridLayout.astro
当前首页和文章页走不同布局:
| 页面 | 布局 |
|---|---|
| 首页 | 单列内容,顶部 Hero |
| 文章页 | 中间正文 + 右侧目录 |
| 关于页 / 归档页 | 普通内容布局 |
判断文章页:
const isPostPage = Astro.url.pathname.startsWith(url("/posts/"));
首页是否显示 Hero:
const isHomePage = pathsEqual(Astro.url.pathname, url("/"));
const showHomeBanner = siteConfig.banner.enable && isHomePage;
页面最大宽度在:
src/constants/constants.ts
export const PAGE_WIDTH = 96;
首页每页文章数量也在这里:
export const PAGE_SIZE = 8;
这些属于「常改但不要乱改」的参数。宽度改太大,正文阅读会散;每页文章太多,首页加载和浏览节奏都会变差。
十三、接入部署检查
每次改完先跑:
pnpm verify
EdgeOne Pages 这类静态托管平台通常填:
构建命令:pnpm run build
输出目录:dist
包管理器:pnpm
推送流程:
git status
git add src/content/posts/my-note.md src/assets/images/posts/my-note.webp
git diff --cached
git commit -m "更新博客"
git push
如果平台已经绑定仓库,git push 后会自动构建。
十四、常见故障速查
| 现象 | 原因 | 处理 |
|---|---|---|
| 首页封面不显示 | image 路径写错 |
从文章文件位置计算相对路径 |
| 本地正常,构建失败 | Astro 动态图片没找到 | 检查 src/assets/images/posts/<slug>.webp 是否存在 |
| Giscus 不显示 | 仓库或分类 ID 错 | 重新到 giscus.app 生成配置 |
| 评论每篇文章串在一起 | mapping 配错 |
个人博客建议用 pathname |
| 音乐播放器没出现 | CDN 被拦或脚本未加载 | 检查浏览器 Network 和 Console |
| TOC 点击不跳转 | 标题 slug 或 Swup 容器问题 | 检查 rehype-slug 和 #toc 容器 |
| 构建后搜索无结果 | Pagefind 没跑 | 确认 build 命令包含 pagefind --site dist |
构建时看到这些不一定是错误:
Pagefind doesn't support stemming for the language zh-cn.
意思是中文没有英文那种词根匹配,搜索仍然可用。
Browserslist: browsers data (caniuse-lite) is old.
意思是浏览器兼容性数据库旧了,不会直接导致构建失败。
十五、几条实用经验
- 先改配置,再改组件:标题、导航、头像、链接都在
src/config/,不要一开始就钻组件。 - 首页只负责气质:轮播图、头像、emo 文案、个人标签够了,不要把所有功能都塞进首屏。
- 文章卡片不要展示太多字段:标题、摘要、日期、字数、标签已经足够。
- 详情页顶部要克制:技术教程读者是来看内容的,不是看一堆元信息。
- 评论单独做组件:以后从 Giscus 换到别的系统,只改
Giscus.astro和详情页引用。 - 音乐播放器全站挂载:放在
Layout.astro,不要每个页面重复引。 - 所有常改点加标记:代码里用
★ 常改标出来,几个月后回来还能快速找到入口。 - 写新教程前先看规范:
src/content/posts/tutorial-style-guide.md是文章风格基准,标题、表格、代码块、结尾都按它来。