这一篇是给群里问"具体怎么搭、内容怎么跑、技术细节是啥"的朋友看的实操版。前文讲了战略层的反思,这里把项目里能拿出来的工程化细节一次性都贴出来。
项目本身还在迭代,所以游戏名、域名、具体词条都做了脱敏,但工程结构、数字、踩坑细节都保持原样,方便照着抄。
一、项目档案
二、内容架构:双轨制(结构化数据 + MDX 攻略)
1. 三张结构化大表,单独占据高权重落地页
我没有把所有内容都塞进 blog,而是把"枚举性内容"抽成 JSON,单独建了三个 SEO 友好的目录页:
代码块
Plain Text
复制
/weapons ← data/weapons.json (259 条)
/bosses ← data/bosses.json ( 92 条)
/mounts ← data/mounts.json ( 28 条)
实现非常简单粗暴,页面直接 import bossesData from '@/../data/bosses.json',然后服务端渲染整张列表 + 客户端过滤器(src/components/bosses/boss-filters.tsx)。这些页在 sitemap 里 priority: 0.8 / changeFrequency: weekly,是除首页之外最高的。
2. 每条数据带可追溯字段,方便日更覆盖
每条 Boss / 武器 / 坐骑记录里都有一个 verification 块(字段是结构化的,词条名做了脱敏):
代码块
JSON
复制
{
"slug": "boss-alpha",
"name": "Boss Alpha",
"region": "Region A",
"difficulty": "Hard",
"drops": [
{ "name": "Drop Item A", "quantity": 1 },
{ "name": "Drop Item B", "quantity": 7 }
],
"guideSlug": "how-to-beat-boss-alpha",
"image": "/images/bosses/boss-alpha.png",
"verification": {
verification.status 让我可以做"哪些条目还没经过权威源核对"的看板,避免一刀切覆盖 AI 生成的脏数据。
3. 文章和结构化数据是双向链接的
- • Boss 详情页里点 "How to Beat" → 跳到 content/blog/how-to-beat-{slug}.mdx
- • 攻略文章 frontmatter 通过 boss-guide 等 category 反查回结构化页
这相当于在站内织了一张 SEO 蛛网,结构化页负责吃通用词("<游戏名> bosses"),攻略文章负责吃长尾词("how to beat <boss>")。
三、内容产出节奏:靠"日更 Prompt"半自动化
下面是按 frontmatter date 字段聚合出来的真实发文节奏:
代码块
Plain Text
复制
前期能维持每天 15 篇这种密度,靠的不是手写,而是一份可复用的 Claude Code 日更 Prompt,放在 site-audit/daily-data-update-prompt.md。每天直接整份复制到 Claude Code 里执行。
核心思路是把"运营"这件事写成一段可执行的 spec,包括:
里面有几个细节是踩过坑之后加进去的硬规则:
- • 不要编造数据,搜不到就标 TBD / null
- • 保留 schema:自定义字段必须留、drops 必须是对象数组(不能改成字符串)、image 字段不能删
- • 范围限制:只改三张 JSON + 必要时新增 how-to-beat-*.mdx,绝不动 src/
这套 prompt 是这个项目最有复用价值的资产之一——同样的模式可以套到任何"结构化数据 + 攻略文章"的垂类站(车评、家电、新游戏、Steam 测评聚合等等)。
四、踩坑实录:四件本可以更早抓住流量的事
坑 1:drafts 没接进发布管线,3/25-3/26 只发了 2 篇
游戏首发后的关键期,我用 Claude Code 生成了三批草稿,按日期放在:
代码块
Plain Text
复制
drafts/<author>-2026-03-25/ 17 篇
drafts/<author>-2026-03-26/ ~15 篇
drafts/<author>-2026-03-27/ ~15 篇
但 Fumadocs 的 source.config.ts 只扫 content/blog,drafts 目录里的文件根本没进发布池。GSC 已经在第三天开始衰减,我才在 3/28 发现这个问题,写了 scripts/promote-blog-drafts.mjs:
代码块
JavaScript
复制
// scripts/promote-blog-drafts.mjs(节选思路)
// 1. 遍历 drafts/<author>-YYYY-MM-DD/*
// 2. 校验是否在 OPERATIONAL_STEMS 黑名单里(index/research/freshness-log 这些不发)
// 3. 重写 frontmatter:补 description / image / published:true / categories / author
// 4. 拷贝到 content/blog/<slug>.mdx
// 5. 输出 promoted-articles.md 报告到 site-audit/2026-03-28/
修完后才回到每天 15 篇的节奏。但流量曲线已经掉过去了——GSC 上是不可逆的。
坑 2:图片管线在 Cloudflare 上彻底翻车(187 个引用 / 19 个 404)
我前期用 Next.js 默认的 next/image 走 /_next/image?url=… 链路。这套在 Vercel 上没问题,但部署到 Cloudflare Workers + OpenNext 之后开始大面积异常:
4/12 用扫描器一查:
- • 仓库内 /images/* 引用 187 个
- • HEAD 校验:168 个 200,19 个 404
- • 缺失的全是 boss guide 封面:cover-{boss-a}.jpg、cover-{boss-b}.jpg 等
根因有三层:
- 1. next/image 在 Workers 生产环境对内容图走优化链路不稳定 → 页面正常但图 broken
- 2. 本地 pnpm dev 强开 remoteBindings: true,访问 /images/* 时尝试连 Cloudflare 远程 dev proxy → ECONNREFUSED 卡 10-20 秒后 500
- 3. R2 里这批对象本身也有缺口(没全量上传)
解法是绕开 next/image,自己做一条 R2 直通管线:
代码块
TypeScript
复制
// src/app/r2-images/[...path]/route.ts
export const dynamic = 'force-dynamic';
export const revalidate = false;
export async function GET(request: Request, context: RouteContext) {
const { path } = (await context.params) as { path?: string[] };
return createR2ImageResponse(request, (path ?? []).join('/'));
}
createR2ImageResponse 里通过 getCloudflareContext() 拿到 env.IMAGES_BUCKET(R2 binding),直接把对象流回去;命中条件下 set cache-control / etag / last-modified / content-length。
然后在 next.config.ts 里做了两件事:
代码块
TypeScript
复制
最后写了 scripts/upload-r2-images.ts 把 8 个根目录批量 sync 到 R2:
代码块
TypeScript
复制
const DEFAULT_SCAN_ROOTS = [
{ directoryPath: 'public/images', keyPrefix: undefined },
{ directoryPath: 'public/blog', keyPrefix: 'blog' },
{ directoryPath: 'public/weapons', keyPrefix: 'weapons' },
{ directoryPath: 'public/mounts', keyPrefix: 'mounts' },
{ directoryPath: 'public/avatars', keyPrefix: 'avatars' },
{ directoryPath: 'public/banners', keyPrefix: 'banners' },
{ directoryPath: 'public/bosses', keyPrefix: 'bosses' },
{ directoryPath: 'public/docs', keyPrefix: 'docs' },
] as const;
支持 –concurrency / –dry-run / –skip-existing,配合 verify-r2-images.ts 做对账。
这套折腾耗了我整整两个工作日。本来应该用来发外链、铺 reddit 的时间,全部喂给了基建。这就是前文反思里"重设计、轻迭代"的一个具体注脚。
坑 3:分页路由间歇性 404,CDN 缓存抓瞎
3/28 排查时发现 /blog/page/2 ~ /blog/page/11 间歇返回 404。本地 build 产物是有的(.next/server/app/en/blog/page/2.html ~ page/11.html),但线上 hit 不上。
最终在 src/app/sitemap.ts 里强制把所有 paginated 路径补进 sitemap,让 Google 直接拿到完整 URL 列表,绕过缓存层的不一致:
代码块
TypeScript
复制
const totalPages = Math.max(1, Math.ceil(posts.length / websiteConfig.blog.paginationSize));
for (let page = 2; page <= totalPages; page++) {
sitemapList.push({
url: getUrl(`/blog/page/${page}`),
lastModified: now,
changeFrequency: 'weekly',
priority: 0.5,
alternates: { languages: generateHreflangUrls(`/blog/page/${page}`) },
});
}
类别页同理处理。paginationSize = 6 也是审计后调出来的——太小会造成上百个分页,太大首屏卡。
坑 4:Orama 搜索 build 失败拖累整个部署
src/app/api/search/route.ts 里 Orama 默认对 ja/ko 启了原生 tokenizer,但 Cloudflare 构建环境装的 @orama/tokenizers 不带这两个语种 → 每次 pnpm build:cf 失败。
最终把 ja/ko 都 fallback 到 English tokenizer 才稳定。这是一个非常典型的"模板里看起来很专业的多语言能力,实际部署到 Workers 就炸"的坑。
五、可复用的"硬基建清单"
如果你也想搭这种结构化数据 + 攻略文章的垂类站,下面是这个项目沉淀出来的可直接抄走的几样东西:
六、如果重来一次:技术节奏的具体调整
设计图迭代 7、8 版那段,对应的成本是约 2 个工作日 + 错过 GSC 黄金期前 48 小时,这是这次项目最贵的一笔账。
七、写给后端程序员的话
这个项目教我一件事:后端的工程化优势,必须配套"内容 / 增长"上的产品机制,才能在 SEO 时代之后还吃到红利。
我做的所有事——R2 直通、verification 字段、日更 prompt、sitemap 自动补全分页——都是正确但慢一个身位的事。它们能让网站长期可维护,但救不了一波流热点项目的窗口期。
下一次,技术底子继续保留,但要在 Day 0 留 50% 精力给:
- 1. 能让用户主动截图分享的产品机制(不是攻略,是测试 / 工具 / 计算器这种交互形态)
- 2. 多平台分发的冷启动(reddit、小红书、X、Discord,比 Google 早至少 72 小时进场)
- 3. 每日一段产品 + 增长的小复盘,而不是 commit log 里又一个 fix: image broken
技术做得再扎实,没有正反馈,路就走不远。看清了高手的底牌,下一次,换我们降维打击。











暂无评论内容