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 是文章风格基准,标题、表格、代码块、结尾都按它来。