Firefly 博客接入友链朋友圈:从零开始的完整部署教程
- 1导航栏 UI 改造全记录:卡片边框、悬浮胶囊、三段收拢与 PC 收拢态快捷面板(附完整复现指南)
- 2博客性能优化全记录:Lighthouse 报告拆解到帧率根因排查(A–F 六项实战)
- 3Firefly 博客实战:从零搭建任意设备可用的发布台(Vercel + GitHub API)
- 4评论区回复时表情面板被截断:一次 overflow 裁剪排查实录
- 5Firefly 博客实战:给文章页加阅读进度条与圆环百分比
- 6Firefly 博客接入友链朋友圈:从零开始的完整部署教程本文
- 7从零开始搭建个人图床
- 8文章中添加音乐播放器
- 9基于Firefly主题的列表封面位置改造
- 10基于rehype的博客PDF嵌入组件实现
- 11Twikoo评论系统部署踩坑记录:Vercel域名无法访问的解决之路
- 12这是我的第一篇BLOG

Firefly 博客接入「友链朋友圈」完整部署教程
适用对象:基于 Firefly(Astro) 主题的静态博客 + Friend-Circle-Lite(下称 FCL)友圈数据服务。 本文是实战复盘:每一步、每一处代码改动都来自真实部署过程,照做即可从零跑通。 预计耗时:1~2 小时。难度:会复制粘贴、会用命令行敲几条命令即可(文中有兜底的网页操作替代方案)。
0. 先搞懂原理:三个部分怎么协作
友链朋友圈不是”装进博客的插件”,而是一条独立的数据流水线:
┌────────────────────────────┐│ ① 你的博客仓库 │ 展示层│ Firefly (Astro) │ /fcircle/ 页面│ 每次构建生成 friend.json ───┼──┐ (你的友链名单)└────────────────────────────┘ │ ▼┌────────────────────────────┐ 每 4 小时爬一次│ ② 你的 FCL fork 仓库 │◄─┘ 读取 friend.json│ GitHub Actions 定时爬虫 │──► 抓取每个友链的 RSS│ 产出 all.json / link.json │──► 推送到 page 分支└──────────────┬─────────────┘ │ git push (自动) ▼┌────────────────────────────┐│ ③ Vercel 数据站 │ 托管层│ 托管 page 分支的静态文件 │ https://fc.你的域名/│ 提供 all.json (带 CORS 头) │└──────────────┬─────────────┘ │ fetch 跨域读取 ▼ /fcircle/ 页面渲染友圈文章流职责划分:
| 部分 | 负责什么 | 技术栈 |
|---|---|---|
| ① 博客仓库 | 新增 /fcircle/ 页面;构建时生成友链名单 friend.json | Astro + Firefly 主题 |
| ② FCL fork 仓库 | 每 4 小时定时爬取友链 RSS,生成数据文件 | Python + GitHub Actions(免费) |
| ③ Vercel | 把数据文件托管成 HTTPS 静态站(带 CORS 头) | Vercel 免费版 |
最终效果:博客上出现「友圈」菜单和 /fcircle/ 页面,自动聚合所有友链的最新文章,每 4 小时更新一次,全程无需人工干预。
1. 准备工作
开始前确认你有:
- GitHub 账号 —— 托管博客仓库和 FCL fork
- Vercel 账号 —— 免费版即可(vercel.com 注册,可用 GitHub 登录)
- Cloudflare 账号 + 你自己的域名 —— 博客已解析在 Cloudflare 上(本文以
mstzuomu.space为例,换成你自己的)国内访问者无法解析
*.vercel.app(被 DNS 污染),必须绑自定义域名,否则访客拉不到数据。 - 本机环境:
- Node.js ≥ 22、pnpm ≥ 11(你跑得动
pnpm dev就说明够了) - Git 基本操作(add / commit / push)
- 浏览器
- Node.js ≥ 22、pnpm ≥ 11(你跑得动
命令行注意(Windows):教程里的 curl 命令请写成 curl.exe。PowerShell 里裸写 curl 会别名到 Invoke-WebRequest,参数不兼容会报错。
2. 第一部分:博客侧代码改造
改动清单(共 10 处,先看全貌再逐个做):
| # | 文件 | 操作 | 作用 |
|---|---|---|---|
| 2.1 | scripts/generate-friend-json.ts | 新增 | 从友链配置生成 FCL 需要的 friend.json |
| 2.2 | package.json | 修改 | 把生成脚本挂进 pnpm build |
| 2.3 | public/friend.json | 生成 | 执行脚本产出(会被发布到站点根目录) |
| 2.4 | src/config/fcircleConfig.ts | 新增 | 友圈页面配置(数据站地址等) |
| 2.5 | src/config/index.ts | 修改 | 导出新配置 |
| 2.6 | src/pages/fcircle.astro | 新增 | 友圈页面本体 |
| 2.7 | src/types/siteConfig.ts + src/config/siteConfig.ts | 修改 | 页面开关 pages.fcircle |
| 2.8 | src/config/navBarConfig.ts | 修改 | 导航栏「社交 → 友圈」菜单 |
| 2.9 | public/fclite/fclite.js、fclite.css | 新增 | FCL 前端渲染脚本(自托管 + 按 2.9 做主题化改造) |
| 2.10 | — | 验证 | 本地跑通 → 提交推送 → 线上验证 |
2.1 新增 scripts/generate-friend-json.ts
FCL 的爬虫只认一种友链格式:{"friends": [["站点名", "网址", "头像"], ...]}。Firefly 的友链写在 src/config/friendsConfig.ts 里,所以需要一个脚本做转换。
新建文件,完整内容:
import fs from "node:fs/promises";import path from "node:path";import { getEnabledFriends } from "../src/config/friendsConfig";
// 生成 Friend-Circle-Lite 所需的友链数据文件// 格式: { "friends": [["站点名", "站点地址", "头像地址"], ...] }// 产物发布到站点根目录 (public/friend.json),供 FCL 的 Action 定时拉取
const OUTPUT_FILE = path.join("public", "friend.json");
async function main() { const friends = getEnabledFriends();
const data = { friends: friends.map((item) => [item.title, item.siteurl, item.imgurl]), };
await fs.mkdir(path.dirname(OUTPUT_FILE), { recursive: true }); await fs.writeFile(OUTPUT_FILE, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
console.log(`friend.json 已生成: ${OUTPUT_FILE} (${friends.length} 个友链)`);}
main().catch((error) => { console.error("生成 friend.json 失败:", error); process.exit(1);});它只导出
enabled: true的友链,与/friends/页面看到的列表保持一致。以后你增删友链,只要正常改friendsConfig.ts,其余全自动。
2.2 修改 package.json
打开 package.json 的 scripts,做两处修改:
改动 1:build 命令最前面插入生成步骤:
"build": "npx tsx scripts/generate-github-card-data.ts && ...""build": "npx tsx scripts/generate-friend-json.ts && npx tsx scripts/generate-github-card-data.ts && ..."改动 2:新增一个手动执行的命令(放在 lqips 旁边):
"lqips": "npx tsx scripts/generate-lqips.ts","friend-json": "npx tsx scripts/generate-friend-json.ts",2.3 执行脚本生成 public/friend.json
pnpm friend-json预期输出:
friend.json 已生成: public\friend.json (N 个友链) ← N 为你的友链数量打开 public/friend.json 确认内容长这样:
{ "friends": [ [ "MIFENG BLOG", "https://blog.imbee.top/", "https://blog.imbee.top/images/logo/logo.webp" ] ]}2.4 新增 src/config/fcircleConfig.ts
// 友链朋友圈 (Friend-Circle-Lite) 页面配置export const fcircleConfig = { // 页面标题 title: "友链朋友圈",
// 页面描述 description: "聚合朋友们的最新文章,数据来自各位友链的 RSS 订阅",
// FCL 数据站地址(fork 仓库 page 分支部署后的站点根地址,末尾必须带 /) // 部署完成后把这里改成你自己的地址,例如 https://xxx.vercel.app/ apiUrl: "https://YOUR-FCL-DOMAIN.vercel.app/",
// 每次加载文章数量 pageSize: 24,
// 头像加载失败时的默认图片 errorImg: "/favicon/sakura2.png",};⚠️ 先保持占位符不动,
apiUrl在第 6 章拿到数据站地址后再回填(本文示例最终填的是https://fc.mstzuomu.space/)。
2.5 修改 src/config/index.ts
加一行导出(保持字母序,放在 dynamicConfig 之后):
export { dynamicConfig } from "./dynamicConfig"; // 动态页面配置export { fcircleConfig } from "./fcircleConfig"; // 友链朋友圈页面配置2.6 新增 src/pages/fcircle.astro(页面本体)
完整内容:
---import { Icon } from "astro-icon/components";import { fcircleConfig, siteConfig } from "@/config";import MainGridLayout from "@/layouts/MainGridLayout.astro";
// 检查页面是否启用if (!siteConfig.pages.fcircle) { return Astro.redirect("/404/");}
const title = fcircleConfig.title;const description = fcircleConfig.description;---
<MainGridLayout title={title} description={description}> <div class="flex w-full rounded-(--radius-large) overflow-hidden relative min-h-32" > <div class="card-base z-10 px-9 py-6 relative w-full"> <!-- 页面标题和描述 --> <div class="mb-4"> <div class="flex items-center gap-3 mb-3"> <div class="h-8 w-8 rounded-lg bg-(--primary) flex items-center justify-center text-white dark:text-black/70" > <Icon name="material-symbols:public" class="text-[1.5rem]" /> </div> <div class="text-3xl font-bold text-neutral-900 dark:text-neutral-100" > {title} </div> </div> { description && ( <p class="text-base text-neutral-600 dark:text-neutral-400 leading-relaxed mb-4"> {description} </p> ) } </div>
<!-- Friend-Circle-Lite 友圈文章流 --> <link rel="stylesheet" href="/fclite/fclite.css" /> <div id="friend-circle-lite-root"></div> <script is:inline define:vars={{ apiUrl: fcircleConfig.apiUrl, pageSize: fcircleConfig.pageSize, errorImg: fcircleConfig.errorImg, }} > window.UserConfig = { private_api_url: apiUrl, page_turning_number: pageSize, error_img: errorImg, }; </script> <script is:inline src="/fclite/fclite.js"></script> </div> </div>
<!-- fclite.css 依赖 [data-theme=light]/[data-theme=dark] 切换亮暗色, 而本站的 data-theme 是代码高亮主题名(one-light/one-dark-pro), 亮暗切换实际由 html.dark 控制,这里做一层变量桥接。 --> <style is:global> #friend-circle-lite-root { --text-color: var(--text-color-light); --background-color: var(--background-color-light); --tag-bg-color: #bfbfbf; --container-bg-color: var(--container-bg-color-light); /* 副文本(日期、统计标签、页脚)统一到主题元信息色 */ --author-color: var(--content-meta); --shadow-color: var(--shadow-color-light); --border-color: var(--border-color-light); --modal-bg-color: rgba(255, 255, 255, 0.5); --modal-content-bg-color: rgba(239, 250, 255, 0.5); --load-more-btn-bg-color: var(--container-bg-color); /* 跟随站点主题色 */ --hover-color: var(--primary); }
:root.dark #friend-circle-lite-root { --text-color: var(--text-color-dark); --background-color: var(--background-color-dark); --tag-bg-color: #474747; --container-bg-color: var(--container-bg-color-dark); --author-color: var(--content-meta); --shadow-color: var(--shadow-color-dark); --border-color: var(--border-color-dark); --modal-bg-color: rgba(0, 0, 0, 0.3); --modal-content-bg-color: rgba(20, 20, 20, 0.5); --load-more-btn-bg-color: var(--container-bg-color); } </style></MainGridLayout>几个关键点解释(看懂可以更好 DIY):
define:vars会把fcircleConfig里的值序列化进内联脚本,所以改配置文件就能改页面行为,不用动这个文件。- 顺序很重要:
window.UserConfig的<script>必须写在fclite.js之前——fclite.js加载时会立即读取UserConfig,读不到会直接报错。Firefly 的 Swup 页面切换会在每次进入页面时重新执行容器内脚本,所以从别的页面切过来也能正常渲染。 - 底部
<style is:global>是亮暗色桥接:FCL 原版样式用[data-theme=light/dark]切色,而 Firefly 的data-theme装的是代码高亮主题名(one-light/one-dark-pro),真正控制暗色的是html.dark类。不加这段,暗色模式下友圈样式会错乱。
2.7 页面开关:pages.fcircle
文件 1 src/types/siteConfig.ts,在 pages 类型里加一行:
pages: { booknav: boolean; // 书签导航页面开关 friends: boolean; // 友链页面开关 fcircle: boolean; // 友链朋友圈页面开关文件 2 src/config/siteConfig.ts,在 resolvePageToggles 里加开关:
// 友链页面开关friends: true,// 友链朋友圈页面开关(Friend-Circle-Lite)fcircle: true,开关为
false时:页面自动 404、导航菜单自动隐藏(前提是 2.8 的菜单项配了pageKey)。还支持环境变量覆盖PUBLIC_PAGES_FCIRCLE=false。
2.8 导航栏菜单:src/config/navBarConfig.ts
改动 1,在「社交」子菜单里、友链之后插入:
// 友链LinkPresets.Friends,
// 友链朋友圈LinkPresets.Fcircle,
// 留言LinkPresets.Guestbook,改动 2,在 LinkPresets 表里、Friends 之后新增条目:
Friends: { name: "友链", url: "/friends/", icon: "material-symbols:link-2-rounded", pageKey: "friends",},Fcircle: { name: "友圈", url: "/fcircle/", icon: "material-symbols:public", pageKey: "fcircle",},2.9 自托管 FCL 前端文件 public/fclite/(含主题化改造)
FCL 官方文档让你用 jsdelivr CDN 引脚本——国内不稳定,不要用。改为自托管:
- 打开你 fork 的仓库(第 3 章会讲怎么 fork),进入
main/目录 - 下载其中的
fclite.js和fclite.css两个文件 - 在博客仓库新建目录
public/fclite/,把两个文件放进去
(也可以从上游仓库拿:https://github.com/willow-god/Friend-Circle-Lite 的 main/ 目录。)
拿到的原版文件是 FCL 自带画风(8px 圆角、灰底卡片、方角按钮),和 Firefly 主题不搭,而且不接入主题的卡片设置——拖透明度滑块、开关卡片边框对它完全无效。所以还要做两轮本地化改造(本文实战时改的,改处都留了「本地修改」注释方便日后比对):
改造 ①:卡片接入主题卡片系统(card-base)
原理:Firefly 的卡片透明度、卡片边框开关、主题色相底色全部通过 .card-base 类挂钩(规则在 src/styles/main.css:.wallpaper-transparent .card-base 用 !important 吃掉一切自写背景)。FCL 原版自己写死了背景/圆角/边框,把主题系统的值全压掉了。改法 = 给卡片加上 card-base 类 + 删掉它自写的那三样属性。
fclite.js 两处:
const randomArticleContainer = document.createElement('div');randomArticleContainer.id = 'random-article';randomArticleContainer.classList.add('card-base');card.className = 'card';card.className = 'card card-base';fclite.css 两处(只删下面标 - 的属性行,其余保留):
#random-article { display: flex; ... background-color: var(--container-bg-color); border-radius: 8px; border: 1px solid var(--border-color); padding: 20px;.card { background-color: var(--container-bg-color); border-radius: 8px; padding: 12px; border: 1px solid var(--border-color); position: relative;改完后的效果:底色 = var(--card-bg)(透明壁纸模式下自动改用滑块控制的 --card-bg-transparent)、圆角 = --radius-large(1rem,与全站卡片一致)、边框跟随「卡片边框」开关,开启时 hover 仍会变主题色高亮(原 fclite 的 hover 规则保留)。
改造 ②:内层元素统一到主题设计语言
以下都在 fclite.css 里改。颜色一律用主题变量(用之前先在 src/styles/variables.styl 里核对存在,防止幽灵变量),对照表:
| 选择器 | 对应页面元素 | 关键改动 |
|---|---|---|
.stat-item | 订阅/活跃/文章/失败 四个统计块 | 底色 → var(--btn-regular-bg);边框 → 1px solid var(--line-divider);圆角 → var(--radius-xl);hover 边框 → color-mix(in srgb, var(--primary) 30%, transparent) |
.card-author、.random-author、.random-date | 名字/日期胶囊 | 改成 category-pill 同款:底 color-mix(in srgb, var(--btn-content) 6%, transparent)、边框 color-mix(in srgb, var(--btn-content) 12%, transparent)、border-radius: var(--radius-full)、文字 var(--btn-content);hover 边框/文字 → var(--primary) |
.random-button-container a | 「换一篇」按钮 | 底 var(--btn-regular-bg)、边框 var(--line-divider)、圆角 var(--radius-full)、文字 var(--btn-content);hover 底 var(--btn-regular-bg-hover) |
.random-link-button | 「阅读文章」按钮 | border-radius: var(--radius-full)(底色本来就是 var(--primary),不动) |
#load-more-btn | 「再来亿点」加载按钮 | 套「换一篇」同款 btn-regular 胶囊方案 |
最后回到 2.6 的 fcircle.astro:桥接块里 --author-color 两条映射改成 var(--content-meta)(代码已同步),日期文字、统计标签、页脚这些副文本就统一到主题元信息色,明暗自动适配。
⚠️ 这些改造都在你博客仓库的
public/fclite/里,不影响数据站。若日后从上游重新下载这两个文件,改造需要重新做一遍。
2.10 本地验证 → 推送上线
本地验证(3 条命令):
pnpm check # Astro 诊断(既有的 i18n/anime 报错与本改动无关,可忽略)pnpm lint # Biome 检查pnpm dev # 启动后访问 http://localhost:4321/fcircle/浏览器打开 http://localhost:4321/fcircle/,应看到:标题「友链朋友圈」、友圈数据加载中(此时 apiUrl 还是占位符,报加载失败属正常,第 6 章回填后就好)。
同时检查导航栏「社交」下多了「友圈」菜单。
提交推送:
git add .git commit -m "feat: 新增友链朋友圈页面"git push推送后等 CI 构建完成(几分钟),然后用 curl.exe 线上验证两个关键地址:
# 应返回 200,且内容是你的友链名单curl.exe -sL https://你的域名/friend.json
# 应返回 200curl.exe -o NUL -w "%{http_code}" https://你的域名/fcircle/
friend.json是 FCL 爬虫的输入,必须先上线,第 3 章的爬虫才能读到你的友链。顺序不能反。
3. 第二部分:Fork Friend-Circle-Lite 并配置(4 处改动)
3.1 Fork 仓库
- 打开上游仓库:
https://github.com/willow-god/Friend-Circle-Lite - 点右上角 Fork → Create fork(只 fork
main分支即可) - 克隆到本地(换成你的用户名):
git clone https://github.com/你的用户名/Friend-Circle-Lite.gitcd Friend-Circle-Lite3.2 改动 1:conf.yaml(4 处,最重要)
这个文件是爬虫的总配置。只改值,不动结构:
| 配置项 | 原值(上游) | 改成 | 说明 |
|---|---|---|---|
spider_settings.json_url | "https://blog.liushen.fun/friend.json" | "https://你的域名/friend.json" | 你的友链名单地址 |
link_check.author_url | "blog.liushen.fun" | "你的域名" | 反链检测用(只填域名不带 https) |
rss_subscribe.enable | true | false | 邮件订阅功能,用不到就关 |
rss_subscribe.github_username | willow-god | 你的GitHub用户名 | 顺手改掉 |
rss_subscribe.your_blog_url | https://blog.liushen.fun/ | https://你的域名/ | 顺手改掉 |
rss_subscribe.website_info.title | "清羽飞扬" | "你的博客标题" | 顺手改掉 |
对照 diff:
spider_settings: enable: true json_url: "https://blog.liushen.fun/friend.json" json_url: "https://你的域名/friend.json" article_count: 5
link_check: ... enable_backlink_check: true author_url: "blog.liushen.fun" author_url: "你的域名"
rss_subscribe: enable: true github_username: willow-god enable: false github_username: 你的GitHub用户名 github_repo: Friend-Circle-Lite your_blog_url: https://blog.liushen.fun/ your_blog_url: https://你的域名/ ... website_info: title: "清羽飞扬" title: "你的博客标题"这是整个部署里最容易漏、漏了后果最严重的一步。 如果忘了改
json_url,爬虫会去爬上游作者的 200 多个友链,你的友圈页面会显示一堆陌生人(本文实战就踩过这个坑,第 9 章有排查方法)。
3.3 改动 2:新增 static/vercel.json(CORS 跨域头)
浏览器从你的博客域名跨域请求 Vercel 数据站,对方必须返回 Access-Control-Allow-Origin: *,否则数据拉不下来。
新建文件 static/vercel.json,完整内容:
{ "headers": [ { "source": "/(.*)", "headers": [ { "key": "Access-Control-Allow-Origin", "value": "*" }, { "key": "Access-Control-Allow-Methods", "value": "GET, POST, OPTIONS, PUT, DELETE" }, { "key": "Access-Control-Allow-Headers", "value": "*" } ] } ]}为什么放
static/而不是仓库根目录?因为 Actions 工作流会把static/里的指定文件拷贝进page分支,而 Vercel 部署page分支时只认分支根目录的vercel.json。下一步就是把这个文件加进拷贝清单。
3.4 改动 3:.github/workflows/friend_circle_lite.yml
找到 Build static publish directory 步骤里的 cp 命令,在清单里加上 ./static/vercel.json:
- name: Build static publish directory run: | mkdir pages cp -r main ./static/edgeone.json ./static/_headers ./static/index.html ./static/readme.md ./static/favicon.ico ./static/bg-light.webp ./static/bg-dark.webp all.json link.json errors.json pages/ cp -r main ./static/edgeone.json ./static/_headers ./static/vercel.json ./static/index.html ./static/readme.md ./static/favicon.ico ./static/bg-light.webp ./static/bg-dark.webp all.json link.json errors.json pages/3.5 改动 4:.gitignore
仓库的 .gitignore 忽略了所有 *.json(防止数据文件进主分支),需要给 vercel.json 开例外,否则它永远推不上去:
# 忽略数据文件*.json!edgeone.json!vercel.json3.6 提交并推送
git add conf.yaml .gitignore .github/workflows/friend_circle_lite.yml static/vercel.jsongit commit -m "fix: 友链数据源指向本站并为 page 分支添加 Vercel CORS 配置"git push⚠️ 核对清单:
git status确认这 4 个文件都进了提交。实战中曾出现”以为推了实际没推”的情况,导致爬虫用了上游的友链列表。推完可以去 GitHub 网页上点开conf.yaml确认json_url已经是你的域名。
4. 第三部分:启用 GitHub Actions,生成 page 分支
FCL 的数据完全由 GitHub Actions 定时生成,部署在免费额度内(公开仓库无限,私有仓库每月 2000 分钟,这个爬虫每次不到 2 分钟,绰绰有余)。
4.1 启用工作流(fork 有两道开关,都要点!)
开关 ①:打开 https://github.com/你的用户名/Friend-Circle-Lite/actions
顶部会有条黄色提示 “Workflows aren’t being run on this forked repository…” → 点 “I understand my workflows, go ahead and enable them”。
开关 ②(很多人卡在这):左侧工作流列表里,Friend Circle Lite 可能仍显示 「已禁用」(fork 特有的 disabled_fork 状态)——
→ 点击左侧的 Friend Circle Lite → 工作流页面点绿色 “Enable workflow” 按钮。
只启用开关 ① 不启用开关 ②,手动触发会报
422 Cannot trigger a 'workflow_dispatch' on a disabled workflow。
4.2 手动运行一次
- 左侧点 Friend Circle Lite
- 右上角 Run workflow → 下拉选
main→ 点绿色 Run workflow - 等 1~2 分钟刷新,看到绿色 ✅ success 即可(首次运行要下载依赖,慢一点正常)
这次运行会:
- 读取你博客上线的
friend.json(你的友链名单) - 检测每个友链可达性 + 抓取 RSS 文章
- 生成
all.json/link.json/errors.json - 强制推送
page分支(首次创建)
4.3 验证 page 分支
打开 https://github.com/你的用户名/Friend-Circle-Lite/tree/page,确认存在:
all.json(点开能看到"friends_num": 你的友链数)link.jsonvercel.json(重点:没有它 CORS 会挂)main/、index.html等静态文件
5. 第四部分:Vercel 部署数据站
5.1 导入仓库
- 登录 vercel.com → Add New… → Project
- 在仓库列表里找到
Friend-Circle-Lite点 Import(首次会让你安装/授权 Vercel 的 GitHub App,按提示授权) - 构建配置全部保持默认即可(
main分支没有构建步骤,仓库根目录的vercel.json里deploymentEnabled.main: false会让它跳过 main 分支的部署——这是上游作者设计好的) - 等导入完成
5.2 把生产分支改成 page(关键步骤)
进入项目 → Settings → Environments → Production,找到 Branch Tracking:
- 把分支从
main改成page→ 点 Save
两个坑:
- 报错
Branch "page" not found in the connected Git repository—— 说明第 4 章还没跑成功、page分支不存在。回去先完成第 4 章再来。 - 新版 Vercel 界面里没有单独的 “Production Branch” 选项(旧教程会误导你去 Git 页找)——它就在上面说的 Environments → Production → Branch Tracking 里。找不到就用 Settings 页左侧顶部的 Find 搜索框搜
production。
5.3 首次生产部署
保存成功后 Vercel 通常会弹出 Redeploy 对话框:
- 环境选 Production
- 源部署选带
page分支标记的那条(列表里最新的) - 点 Redeploy
如果没弹窗,去 Deployments 页找到 page 分支的那条部署 → 右侧 … → Promote to Production。
完成后,项目的生产地址(形如 friend-circle-lite-xxx.vercel.app)就由 page 分支驱动了——以后 Actions 每次推送,Vercel 自动部署,无需任何手动操作。
5.4 绑定自定义子域名(国内必做)
为什么必须:
*.vercel.app域名在国内被 DNS 污染(解析出假 IP),你的访客会全部加载失败。实测必须绑自有域名。
第 1 步 — Vercel 侧添加域名:
项目 → Settings → Domains → 输入你想用的子域名(例:fc.你的域名)→ Add。
添加后 Vercel 会提示需要一条 CNAME 记录(目标是 cname.vercel-dns.com)。
第 2 步 — Cloudflare 侧加 DNS 记录:
Cloudflare 控制台 → 你的域名 → DNS → Records → Add record:
| 字段 | 填写 |
|---|---|
| Type | CNAME |
| Name | fc(对应 fc.你的域名 的前缀) |
| Target | cname.vercel-dns.com |
| Proxy | DNS only(一定选灰云,不要开橙色云代理) |
| TTL | Auto |
第 3 步 — 回 Vercel 等证书签发:
DNS 生效通常 1~5 分钟。如果域名状态显示证书错误,点 Refresh / 重新保存一次域名即可触发重新签发。
验证:
# 应返回 200,并且能看到 Access-Control-Allow-Origin: *curl.exe -sI https://fc.你的域名/all.json响应头里必须有这一行,缺了就回头检查 page 分支上有没有 vercel.json(第 3.3/3.4 步)。
6. 第五部分:回填数据站地址,全线贯通
打开 src/config/fcircleConfig.ts,把占位符换成你的真实数据站地址(末尾的 / 不能少):
// FCL 数据站地址(fork 仓库 page 分支部署后的站点根地址,末尾必须带 /)apiUrl: "https://YOUR-FCL-DOMAIN.vercel.app/",apiUrl: "https://fc.你的域名/",提交推送:
git add src/config/fcircleConfig.tsgit commit -m "feat: 回填友圈数据站地址"git push等博客 CI 部署完成(几分钟),最终验证:
# 1. 页面 HTML 里已包含新地址curl.exe -sL https://你的域名/fcircle/ | findstr "fc.你的域名"
# 2. 数据接口 200 + CORS + 你的友链数curl.exe -sI https://fc.你的域名/all.json浏览器打开 https://你的域名/fcircle/ —— 看到自己朋友们的最新文章,部署完成 🎉
页面如果还显示旧数据/加载失败:FCL 前端有 10 分钟 localStorage 缓存,等 10 分钟再刷新,或开无痕窗口立即查看。
7. 成品验收清单
逐项打勾,全过即部署成功:
| # | 检查项 | 验证方法 | 预期 |
|---|---|---|---|
| 1 | 友链名单已上线 | curl.exe -sL https://你的域名/friend.json | 200,friends 数量 = 你的友链数 |
| 2 | 页面已上线 | 浏览器打开 /fcircle/ | 200,标题「友链朋友圈」 |
| 3 | 导航菜单 | 导航栏「社交」下拉 | 有「友链」和「友圈」两项 |
| 4 | Actions 正常 | fork 的 Actions 页 | 最近一次运行是绿色 success |
| 5 | page 分支 | github.com/你/Friend-Circle-Lite/tree/page | 有 all.json / link.json / vercel.json |
| 6 | 数据站可达 | curl.exe -sI https://fc.你的域名/all.json | 200 + Access-Control-Allow-Origin: * |
| 7 | 数据是你的友链 | 浏览器打开 https://fc.你的域名/all.json | friends_num = 你的友链数,作者都是你朋友 |
| 8 | 文章渲染 | 无痕窗口打开 /fcircle/ | 文章卡片正常显示,点「换一篇」「阅读文章」可用 |
| 9 | 自动更新 | 等一个 cron 周期(≤4 小时)后看 last_updated_time | 时间刷新 |
| 10 | 亮暗色 | 切换博客暗色模式 | 友圈样式跟随切换、无错乱 |
| 11 | 卡片跟随主题设置 | 拖「卡片透明度」滑块、开关「卡片边框」 | 统计面板与文章卡片底色/边框同步变化(未做 2.9 改造则此项不过) |
8. 自动化之后:日常运行说明
部署完成后你什么都不用做,全自动链路:
每 4 小时 (cron: 22 */4 * * * UTC,即北京时间 00:22 / 04:22 / 08:22 / 12:22 / 16:22 / 20:22) └─ GitHub Actions 爬虫运行 ├─ 检测友链可达性(结果缓存 24 小时,不会频繁骚扰友链站点) ├─ 抓取各站 RSS 最新文章 ├─ 生成 all.json / link.json └─ 推送 page 分支 └─ Vercel 自动生产部署 → fc.你的域名 更新 └─ 博客 /fcircle/ 下次访问即是新数据常见日常操作:
- 改了友链配置:改
friendsConfig.ts→ 推送博客(friend.json自动更新)→ 下一个爬虫周期生效。想立即生效就去 fork 的 Actions 手动 Run workflow 一次。 - 想手动立刻刷新数据:fork → Actions → Friend Circle Lite → Run workflow,约 90 秒完成并自动上线。
- 看哪些友链挂了:打开
https://fc.你的域名/(FCL 自带的展示首页有可达性列表),或看博客友圈统计里的「失败」数。
数据字段含义(友圈顶部统计条):
| 字段 | 含义 |
|---|---|
| 订阅 | 友链总数 |
| 活跃 | 可达且能抓到 RSS 的站点数 |
| 文章 | 聚合的文章总数 |
| 失败 | 不可达 / 没有 RSS / 抓取失败的站点数(有几台挂了属正常现象,不是你的部署问题) |
9. 踩坑排查表(本文实战遇到的全部问题)
| 现象 | 原因 | 解决 |
|---|---|---|
| Actions 页显示「无工作流程运行」,列表里工作流带「已禁用」 | fork 仓库有两道开关:仓库级启用 + 单个工作流启用 | 第 4.1 章:先点 “I understand my workflows…”,再进工作流页点 “Enable workflow” |
手动触发报 422 Cannot trigger a 'workflow_dispatch' on a disabled workflow | 同上,第二道开关没开 | 同上 |
Vercel 报 Branch "page" not found | page 分支还不存在 | 先成功运行一次 Actions(第 4 章),再回来保存 |
| 找不到 “Production Branch” 设置 | Vercel 新版 UI 已移动位置 | Settings → Environments → Production → Branch Tracking;或左侧 Find 搜 production |
| 友圈显示的全是陌生人(200+ 订阅,作者都不认识) | conf.yaml 的 json_url 还指向上游作者的友链列表(改动没推送成功) | 改 conf.yaml → git add 确认 → 推送 → 手动 Run workflow → 核对 page 分支 all.json 的 friends_num |
| 友圈页面一直加载中/加载失败 | apiUrl 占位符没回填、末尾漏 /、或数据站 CORS 头缺失 | 第 6 章回填;curl.exe -sI 检查 Access-Control-Allow-Origin: *;确认 page 分支有 vercel.json |
| 数据站地址国内打不开 | *.vercel.app 被 DNS 污染 | 第 5.4 章绑定自定义子域名(CNAME 到 cname.vercel-dns.com,灰云) |
| 页面数据是旧的 | fclite.js 有 10 分钟 localStorage 缓存 + 浏览器缓存 | 等 10 分钟或无痕窗口刷新 |
| 友圈数据突然变回旧数据(或又出现陌生人文章) | Vercel 的 Redeploy 弹窗里选中了历史旧部署并发布,生产被回滚 | Deployments 列表核对最新一条 page 部署的时间 → 对最新的那条执行 … → Promote to Production;别选日期是几天前的部署 |
| 证书签发失败 / 域名一直 Pending | DNS 还没生效 | 等 1~5 分钟,在 Vercel Domains 页 Refresh 重试;确认 Cloudflare 里是灰云 |
PowerShell 里 curl -sI 报”参数无效” | PS5.1 把 curl 别名成 Invoke-WebRequest | 一律写 curl.exe |
pnpm check / type-check 一堆报错 | 项目既有问题(i18n 缺键、anime/vndb 页) | 与本教程无关,只看你新增文件有没有报错 |
pnpm lint 把一堆无关文件改了格式 | biome check --write 会顺手格式化 | git checkout -- <无关文件> 还原,只保留自己的改动 |
本机构建报 unable to verify the first certificate(OG 图片生成) | 本机网络环境的 TLS 证书链问题 | set NODE_OPTIONS=--use-system-ca 后重跑,或忽略(CI 上不会出现) |
本地跑 FCL 爬虫报 ZoneInfoNotFoundError: Asia/Shanghai | Windows 的 Python 缺 tzdata 包 | pip install tzdata(见附录 B) |
10. 安全收尾(推荐)
如果你在部署过程中用过 API Token(比如为了用 CLI 部署临时生成的 Vercel Token):
- vercel.com → Settings → Tokens → 删除用完的 token
- 清理本机临时凭据文件(如果有)
- 检查 fork 仓库:Settings → Actions → General → Workflow permissions 确认是
Read repository contents and permissions(本教程的工作流文件里已显式声明contents: write,够用,无需给默认权限开写)
附录 A:文件改动总清单
博客仓库(FireflyPriWeb):
| 文件 | 操作 |
|---|---|
scripts/generate-friend-json.ts | 新增 |
package.json | 修改(build 链 + friend-json 命令) |
public/friend.json | 脚本生成(勿手改) |
src/config/fcircleConfig.ts | 新增 |
src/config/index.ts | 修改(+1 行导出) |
src/pages/fcircle.astro | 新增 |
src/types/siteConfig.ts | 修改(pages.fcircle 类型) |
src/config/siteConfig.ts | 修改(pages.fcircle 开关) |
src/config/navBarConfig.ts | 修改(Fcircle 菜单项 ×2 处) |
public/fclite/fclite.js、fclite.css | 新增(从 fork 的 main/ 目录复制,并按 2.9 完成两轮主题化改造) |
FCL fork 仓库:
| 文件 | 操作 |
|---|---|
conf.yaml | 修改(json_url / author_url / rss_subscribe 共 6 处值) |
static/vercel.json | 新增 |
.github/workflows/friend_circle_lite.yml | 修改(cp 清单 +1 文件) |
.gitignore | 修改(!vercel.json) |
平台侧配置(非代码):
| 位置 | 操作 |
|---|---|
| GitHub fork → Actions | 启用仓库 workflows + 启用 Friend Circle Lite 工作流 |
| Vercel 项目 → Environments | Production 分支 = page |
| Vercel 项目 → Domains | 添加 fc.你的域名 |
| Cloudflare DNS | CNAME fc → cname.vercel-dns.com(灰云) |
博客 fcircleConfig.apiUrl | 回填数据站地址 |
附录 B(可选方案):不用 GitHub Actions,本地 Python 爬虫手动部署
适用于:想本地先跑通看看效果,或不想用 Actions 的场景。
cd Friend-Circle-Litepython -m venv .venv.venv\Scripts\python.exe -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple requests feedparser PyYAML jinja2 python-dateutil tzdata.venv\Scripts\python.exe run.py # 生成 all.json / link.json / errors.json注意两点:
- 国内直连 PyPI 会超时,务必加清华镜像
-i https://pypi.tuna.tsinghua.edu.cn/simple;Windows 必须装tzdata。 - 生成后把
main/、static/下的指定文件、vercel.json和三个 json 拷到一个目录,用 Vercel CLI 部署:
npm install vercel --registry=https://registry.npmmirror.com # npmjs 被墙时用 npmmirrorvercel deploy --prod --token=你的token但此方式数据不会自动更新(每次都要手动跑),正式使用推荐本文正文的 GitHub Actions 方案。
附录 C:把本文发布到你的博客
想把这篇教程作为博客文章发布:
pnpm new-post把本文内容粘进生成的 md 文件,补上 frontmatter(标题、日期、分类、标签),放 src/content/posts/ 下推送即可。若你的博客启用了友圈失败站点的评论系统接入,按 Firefly 惯例在 frontmatter 加 comment: true。
教程基于实战部署整理:Firefly 6.16.7 + Friend-Circle-Lite(v2.x)+ Vercel Hobby + Cloudflare DNS。文中示例域名 mstzuomu.space / fc.mstzuomu.space 请替换为你自己的。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!




