Cover image for How I Built a Source-Tracked Game Wiki Network with Astro 5

shi0318

初期のゲームWikiの多くは信頼性の問題を抱えています。公式情報とベータ版の発見、トレイラーの推測、コミュニティの憶測が混在し、読者に対して何が実際に確認された情報なのかを明示していません。私はフォローしているゲームのためにこの問題を解決したいと思い、Astro 5で出典追跡型ゲームWikiの小さなネットワークを構築しました。

この投稿では、アーキテクチャ、コンテンツモデル、そしてネットワーク全体を安価にホストし、簡単に更新できるようにする小さな工夫について説明します。

実際に構築したもの

同じスタックで構築された6つの独立したWiki:

リポジトリはオープンソースなので、コードを確認してパターンを再利用できます:

  • github.com/shi0318/MortalShell2
  • github.com/shi0318/ValorMortis
  • github.com/shi0318/plaguetale
  • github.com/shi0318/beastreincarnationwiki
  • github.com/shi0318/graveseasons
  • github.com/shi0318/StarWarsGalacticRacer

発売前ゲームWikiにAstro 5を選ぶ理由

発売前コンテンツの場合、優先事項は明確です:高速な初回描画、軽量なJS、自動サイトマップ、予測可能なデプロイ、CMSを開かずにコンテンツを書けること。Astro 5はこれらのすべてを満たします。


text
output: 'static',
trailingSlash: 'always',
integrations: [sitemap({ ... })],
vite: { plugins: [tailwindcss()] }
基本的にこれだけで設定は完了です。ランタイム、Workers、データベースは必要ありません。ページはプレーンHTMLとしてレンダリングされ、Cloudflareのエッジから直接配信されます。
信頼度ラベル付きコンテンツモデル
これらのWikiを単なる静的ダンプと異なるものにしているのは、小さなコンテンツ契約です。すべてのガイドファイルはフロントマターにステータスを含みます:
status: official
status: beta
status: trailer
status: series
status: unconfirmed
<StatusBadge>コンポーネントは、そのステータスを各ページに表示されるラベルとしてレンダリングします。<SourceTable>コンポーネントはソースURL、確認日、メモを一覧表示します。読者は、ボス一覧がSteamで確認されたものか、トレイラーから推測されたものか、フォーラムの噂かを一目で確認できます。
コードでは次のようになります:
import { z } from 'astro:content';

const guideSchema = z.object({
  title: z.string().max(60),
  description: z.string().min(50).max(160),
  status: z.enum(['official', 'beta', 'trailer', 'series', 'unconfirmed']),
  sources: z.array(z.object({
    url: z.string().url(),
    date: z.date(),
    note: z.string(),
  })),
});
これにより2つのメリットが得られます。まず、サイトが憶測を事実として暗黙的に公開することはありません。次に、同じスキーマにより、ステータスがGoogleスニペットに値しないほど弱い場合にページをサイトマップから除外できます。
Gitによるページごとのsitemap lastmod
私が最も気に入っている巧妙な工夫は、ページごとのlastmodです。Astroのsitemap統合は、デフォルトでlastmodをビルド時刻に設定します。いずれかのページが変更されるたびに再デプロイされる6つのWikiネットワークでは、これはGoogleにとって弱いシグナルになります。
そこでserializeをオーバーライドして各URLをソースファイルにマッピングし、gitにそのファイルの最終コミットを問い合わせます:
import { execFileSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

function lastmodFor(url) {
  const slug = new URL(url).pathname.replace(/^\/+|\/+$/g, '');
  const candidates = slug === ''
    ? ['src/pages/index.astro']
    : [
        `src/pages/${slug}.astro`,
        `src/pages/${slug}/index.astro`,
        `src/content/guides/${slug}.md`,
        `src/content/guides/${slug}.mdx`,
      ];
  const file = candidates.find(rel => existsSync(join(ROOT, rel)));
  if (!file) return BUILD_TIME;
  try {
    return execFileSync(
      'git',
      ['log', '-1', '--format=%cI', '--', file],
      { cwd: ROOT, encoding: 'utf8' }
    ).trim();
  } catch {
    return BUILD_TIME;
  }
}

sitemap({
  serialize(item) {
    return { ...item, lastmod: lastmodFor(item.url) };
  },
})
結果:実際に編集したページのみがタイムスタンプを更新します。それ以外は変更されません。これにより、CMSなしでGoogleに明確な最新性シグナルを提供できます。
サイトマップから弱いページを除外
憶測ページはページ内探索には役立ちますが、クロール予算を浪費すべきではありません。NOINDEX_URLSの小さな許可リストを保持し、ページのメタタグがフィルタリングされるのと同じ方法でサイトマップをフィルタリングします:
const NOINDEX_URLS = ['/gloom/'];

sitemap({
  filter: page => !NOINDEX_URLS.includes(new URL(page).pathname),
})
これにより、trailerまたはunconfirmedとラベル付けされたページはナビゲーション用に存在できますが、検証済みのページのみがGoogleの競争に参加します。
発売前ゲームにこのネットワークが有効な理由
このパターンを再利用する価値がある3つの理由:
ステータスをファーストクラスの概念として扱う。発売前コンテンツは常に変動します。これをコンテンツスキーマに明示的にモデル化することで、正直な執筆を強制し、公開して安全なコンテンツを自動化できます。

すべてを静的に。DB、API、Workersは不要です。Cloudflare Pagesがネットワーク全体を無料で処理します。

オープンソースを防御壁に。誰でもリポジトリをフォークし、データを入れ替えて、自分のニッチなゲーム向けのWikiを週末に公開できます。これが6つの小さなWikiネットワークがパターンになる理由です。

自分のゲームで試してみたい場合、最も簡単な方法は:
git clone https://github.com/shi0318/MortalShell2.git my-game-wiki
cd my-game-wiki
npm install
npm run dev
その後、src/data/site.tsを編集し、src/content/guides/にページを配置して公開するだけです。
ネットワークの成長をフォローしたい場合は、ローンチハブはMortal Shell II Wikiです。フィードバックとPRは6つのリポジトリのいずれでも歓迎します。

Enter fullscreen mode Exit fullscreen mode