一个游戏攻略站从 0 到 9000 曝光、再到长尾枯萎的完整项目复盘

这一篇是给群里问"具体怎么搭、内容怎么跑、技术细节是啥"的朋友看的实操版。前文讲了战略层的反思,这里把项目里能拿出来的工程化细节一次性都贴出来。

项目本身还在迭代,所以游戏名、域名、具体词条都做了脱敏,但工程结构、数字、踩坑细节都保持原样,方便照着抄。

一、项目档案

二、内容架构:双轨制(结构化数据 + 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. 1. next/image 在 Workers 生产环境对内容图走优化链路不稳定 → 页面正常但图 broken
  2. 2. 本地 pnpm dev 强开 remoteBindings: true,访问 /images/* 时尝试连 Cloudflare 远程 dev proxy → ECONNREFUSED 卡 10-20 秒后 500
  3. 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. 1. 能让用户主动截图分享的产品机制(不是攻略,是测试 / 工具 / 计算器这种交互形态)
  2. 2. 多平台分发的冷启动(reddit、小红书、X、Discord,比 Google 早至少 72 小时进场)
  3. 3. 每日一段产品 + 增长的小复盘,而不是 commit log 里又一个 fix: image broken

技术做得再扎实,没有正反馈,路就走不远。看清了高手的底牌,下一次,换我们降维打击。

© 版权声明
THE END
喜欢就支持一下吧
点赞8 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容