< BACK 次の25,000ページのディレクトリをNext.jsで構築した方法:HostList ポストモーテム――線画イラスト

25,000ページのディレクトリをNext.jsで構築した方法:HostList事後分析

2022年の後半のどこかで、Webホスティングディレクトリを構築するのは簡単だと自分を説得した。データを集約して、ページを生成して、ランク付けして、マネタイズする。シンプルだ。プログラマティックSEOは前にやったことがある。英国のクライアント向けにローカライズされた不動産ツール、月間40k訪問でピークを迎えたSaaS比較サイト。だからHostListなら6週間のプロジェクトだと思ってた。実際には7ヶ月近くかかった。そしていくつかのものを壊しかけた。睡眠スケジュール、ジュニア開発者の一人の自信、そして予算を立てていなかった月180ポンドのVercel請求。

これが事後分析です。LinkedInバージョンではなく、本当のものです。

---

自分自身に書いたブリーフ

HostListはシンプルなはずだった。Webホスティングプロバイダーのディレクトリ。共有、VPS、専有、マネージドWordPress。各プロバイダーの個別ページ、比較ページ、カテゴリページ、ロケーションベースのページ(例えば「ドイツで最高のホスティング」)。計算してみる。約400プロバイダー × 複数のページタイプ × 20以上のフィルター組み合わせ。思ったより早く25,000ページに到達する。

私はほぼ考えずにNext.jsを選びました。SeahawkではReactベースのより大きなビルドの大部分にNext.jsを使っています。エコシステムが成熟していて、getStaticPropsとgetStaticPathsはSEO対策が必要な静的生成に適していて、個人的にはこのスケールではRemixやGatsbyよりもファイルベースのルーティングの方が理解しやすいと感じています。

最初の本当の判断はデータレイヤーだった。ヘッドレスCMSはすぐに除外した。25,000エントリーのContentfulレートを払いたくなかったし、大量のプログラマティック書き込みを適切に処理できるCMSを信頼していなかった。結局SupabaseのPostgresデータベースと、その前に軽量なNext.js APIレイヤーを構築した。その部分は実際にうまくいった。複雑になったのはほぼ他のすべてだ。

---

スケール規模での静的生成:誰も警告してくれないこと

25,000ルートのgetStaticPathsについて言いたいことはこれです。動作します。技術的には。ただし、ビルド時間があなたの人生選択を疑わせることになります。

最初の完全なビルドは4時間47分かかりました。Vercel上で。プランのリミットに注意しないと、午前2時の請求通知を引き起こすようなやつです。スマートフォンからそのSlackアラートを見つめながら、本気でWordPressを使うだけにしようかと考えました。

`fallback: 'blocking'`の罠

最初の本能はすべてをプリレンダリングすることだった。すべてのページ、すべての組み合わせ。悪い考えだ。そして、ほとんどのチュートリアルが警告する理由ではない(通常は「時間がかかる」というだけ)。本当の問題はキャッシュ無効化だ。ホスティングプロバイダーが価格を更新するとき(そして常に更新する)、影響を受けたページを再構築する必要がある。すべてがISRなしで静的にプリレンダリングされていれば、25,000ページのうち30ページに影響するデータ変更のためにフルリビルドをトリガーしている。

最も多くのページではrevalidateを86400秒(24時間)に、料金が多く関わるプロバイダーページでは3600秒に設定してIncremental Static Regenerationに切り替えました。これはプロジェクト全体で最も生活の質を向上させた改善でした。ビルド時間は40分未満に短縮されました。トラフィック優先度による上位約2,000ページだけを事前レンダリングし、残りはfallback: 'blocking'でオンデマンド生成させたからです。

ルートツリーの分割

私が違うことをするだろう、そして今Seahawkの大規模なプログラム的プロジェクトに関わるすべての開発者に言うことは: ルートツリーを早期に分割することです。25,000個のスラッグを返そうとする1つのモノリシックなgetStaticPathsファンクションを持つべきではありません。私たちのものは以下に分割しました:

  1. /providers/[slug]、個別プロバイダーページ(約400)
  2. /compare/[slugA]-vs-[slugB]、ヘッド・ツー・ヘッド比較ページ(約8,000)
  3. /category/[type]、カテゴリランディングページ(約40)
  4. /location/[country]/[type]、地域×カテゴリ組み合わせ(16,000以上)
  5. /best/[use-case]、キュレーション済みリストページ(約600)

各ルートグループは独自のリバリデーション周期、独自のデータ取得ロジック、そして重要なのは独自のビルド優先度を持ちます。ロケーションページはほぼすべてオンデマンド。プロバイダーページは常にプリレンダリング。明確な分離です。

---

データパイプラインの混乱(と我々がどう修正したか)

2023年初頭、私は HostList のデータ収集側を構築する際に大きな過ちを犯しました。Python で書かれたスクレイピングスクリプト(BeautifulSoup と Webshare のローテーティングプロキシプールを使用)、手動の Google Sheet で修正、そして Supabase テーブル。3つの情報源がありました。どれも互いに適切に連携していません。

ジュニア開発者、いい奴だが、ブートキャンプを卒業したばかりで、SheetとSupabaseの間の同期スクリプトを3週間メンテナンスしていた。カラム名が変わるたびに壊れた。1週目にSheetを廃止して、ちゃんとした内部管理UIを構築すべきだった。結局、Next.js APIルートとRetoolダッシュボードを使って構築したが、そこにたどり着くまでに約60時間のエンジニアリング時間を費やした。

解決策:常に単一の情報源。データベースが正規。すべてがデータベースに書き込まれる。管理 UI はデータベースから読み込んで書き込む。スクレイパーはデータベースに書き込む。当たり前に聞こえる。後になると、いつもそうだ。

スケール時のデータ鮮度を保つ

このサイズのディレクトリでは、データの鮮度はUXの問題と同じくらいSEOの関心事だ。Googleはプライシングテーブルがのままずっと £2.99/月 を表示していて、実は8ヶ月前から £5.99 になってることに気づく。こう構築した:

  • Railway クロンで動く週次スクレイプジョブ(安い、信頼性が高い、専用サーバーが不要)
  • price_updated_atカラムが変更されたときに発火し、Next.jsの再検証エンドポイントにヒットするSupabaseデータベースウェブフック
  • Retool内の手動オーバーライドフラグ(スクレイパーを積極的にブロックしてる約30のプロバイダ用)

あのrevalidationエンドポイント、/api/revalidate?secret=TOKEN&path=/providers/sitegroundは、Next.jsの標準機能だが、データベースウェブフックに接続するには多少の配管工事が必要だった。その分の時間は絶対に費やす価値がある。

---

SEOアーキテクチャ:実際に針を動かしたもの

コンテンツサイトを十分に構築してきた経験から、25,000ページを持つことと25,000ページがランクインすることは別だと知っている。比較ページはその罠だった。約400プロバイダーのあらゆるA対Bの組み合わせを生成し、理論上約79,800のペアリングを得た。そのうち約8,000ページを構築した。そしてほとんどのページは、率直に言ってシンだった。

正直な告白だ。欲張ってしまった。SEOロジックは理にかなっていて、「SiteGround vs Bluehost」は本当の検索ボリュームがあり、比較クエリのロングテールは膨大だったが、すべてのページの存在を正当化するために十分なユニークコンテンツを構築しなかった。Googleが比較セクションをクロールし始めて、明らかにそれに費やす価値がないと判断した。Googleのシンコンテンツガイダンスは率直だし、もっと早く自分に率直であるべきだった。

回復のためにしたこと

削減した。比較ページを約8,000ページから約1,200ページに削減し、実証された検索ボリュームがあるペアだけを残した(Ahrefsで検証済み、グローバルで月間最低50検索)。その後、残りのページを以下で充実させた:

  • 構造化されたプロバイダーデータから引き出した動的な「誰に最適か」セクション
  • 実際の稼働時間データ(サードパーティの稼働時間APIと統合)
  • Trustpilotデータから利用可能なユーザーレビューサマリー

その結果、8,000ページの役に立たないものではなく、1,200ページの実際に有用なものになった。比較セクションへのオーガニックトラフィックは続く3か月間で340%増加した。直感に反するまでは直感に反する。

このスケールでの内部リンク

25,000ページあれば、内部リンクを手動では実現できない。getStaticPropsでビルド時にSupabaseをクエリして、カテゴリと地域のオーバーラップに基づいて、もっとも関連性の高い隣接する5ページを返す関連ページコンポーネントを構築した。編集上の介入は必要ない。完璧ではなく、時々VPSホスティングページが少し外れたものにリンクすることもあるが、90%は正しく、初日から全ページに文脈的に関連のある内部リンクがあることを意味した。

パフォーマンス:あなたを謙虚にさせる部分

パフォーマンス:あなたを謙虚にさせる部分

静的生成でパフォーマンスが簡単になると思うかもしれない。概念的には確かにそうだ。事前レンダリングされたHTML、VercelのCDNでエッジキャッシュ、サーバーレンダリングのオーバーヘッドなし。だが25,000ページは、コンポーネントツリーについて悪い判断を下す25,000の機会を意味する。

最大のパフォーマンス問題は、プロバイダ比較表だった。重いクライアント側のReactコンポーネントで、多くの状態、多くの条件付きレンダリング、プロバイダページと比較ページの両方で使用された。モバイルでは、約4.8秒のLargest Contentful Paintを引き起こしていた。悪い。本当に悪い。主なトラフィックが購入の決定中の人々のサイトでは。

サーバーレンダリング型の静的テーブルと、インタラクティブフィルタビットの薄いReactハイドレーション層として再構築した。LCPは1.9秒に低下した。これは魔法ではなく、退屈なことを適切にやることだ。

画像の問題

すべてのプロバイダにはロゴがある。400個のロゴ、スクリーンショット、UI プレビュー、機能アイコン。最初の2ヶ月間、Vercelの組み込み画像最適化でホストするという間違いを犯した。帯域幅コストは静かに恐ろしかった。すべてをCloudflare R2とカスタムドメインに移動させ、Vercelの請求書を月額180ポンドから40ポンドに削減した。画像が多いものを構築している場合は、Cloudflare R2を早期に検討してください。無料エグレスは、スケールで本当に役に立つ。

---

ビルドパイプラインが今実際どのようになっているか

具体的な状況を知りたい人向けに:

  1. データ収集は、Railwayのcronジョブ上のPythonスクレイパーで、Supabase Postgresに書き込む
  2. 管理レイヤー、Retoolダッシュボードで手動編集、修正、プロバイダーフラグ対応
  3. Next.jsアプリ、Pagesルーター(App Routerが十分に安定する前に開始したため)、Vercelにデプロイ
  4. ISR + オンデマンド再検証、上位約2,000ページは事前構築、残りはオンデマンド、すべて24時間再検証
  5. 画像、Cloudflare R2、カスタムサブドメイン経由で配信、Cloudflare CDNが前面に配置
  6. アナリティクス、プライバシーフレンドリーなトラフィックデータはPlausible、ランキング追跡はAhrefs
  7. アップタイム監視、BetterUptimeがトラフィック最多の5ページタイプを監視

これは派手ではありない。また、保守もほぼ退屈だが、それが3年間走らせ続けるインフラストラクチャに求めるものだ。

---

正直な失敗、番号付き

  1. スコープが広すぎた。25,000 ページは常に目標だったが、品質の高い 500 ページでローンチして拡大すべきだった。代わりにすべてでローンチしたため、最初の 4 ヶ月間 Google クローラー予算の問題を抱えていた。
  2. 初日から再検証を適切に設定しなかった。完全なリビルドに 2 ヶ月を浪費したが、ISR があればそれは不要だった。
  3. Google スプレッドシートを保持していた。単一の情報源は 1 週目から譲歩の余地なく設定されるべきだった。
  4. 比較ページの品質を過小評価した。ボリュームは戦略ではない。
  5. Vercel の画像最適化を長く使いすぎていました。6週間遅れて R2 に移行しました。
  6. ルートツリーを十分に早く分割しませんでした。同じ getStaticPaths 呼び出しで高速ルートと低速ルートを混在させてから、ビルドが遅い理由を不思議に思いました。

これらはすべて、当時は合理的に見えた判断だ。チュートリアルが捉えられていない部分がここだ。悪いアーキテクチャ決定は通常、それを下した時点では聞こえがいい正当化理由を持っている。

---

FAQ

初回ビルドでローンチするまでにどのくらいかかった?

最初のコミットから v1 と言える状態まで 7 ヶ月。最初の粗いパブリック版は 4 ヶ月目辺りで公開されていましたが、コンテンツ量が深刻に不足していて、比較セクションはほぼ使い物になりませんでした。「技術的には公開」まで 4 ヶ月、そこから「実際に使える」状態まであと 3 ヶ月といったところです。

今から始めるなら App Router を使いますか?

2023 年後半以降に始める新規プロジェクトであれば、おそらくはい。App Router のサーバーコンポーネントは、この種のデータ量が多いページ生成に実際に適しています。ただし、既存の 25,000 ページの Pages Router アプリを移行するのは、近い将来取り組むプロジェクトではありません。Pages Router はまだ動いていますし、「動く」というのは過小評価されているんです。

ビジネスを辞めたり、サービス内容を大きく変更したプロバイダーにはどう対応していますか?

データベースにステータスフラグ active、deprecated、redirected がある。非推奨プロバイダーは完全削除ではなく、スリムなアーカイブページになる。これでバックリンクが保持される。リダイレクトされたプロバイダー(例:ホストが他方を買収した場合)は301で対応し、next.config.jsのNext.jsリダイレクト設定で処理される。ステータスフラグは月1回見直す。

もう一度やるなら Next.js の代わりに何を使いますか?

正直なところ分かりません。Astro は大部分が静的なコンテンツサイトとしては興味深いし、小規模なプロジェクトで試してみています。ただ Next.js は、同じコードベース内に静的セクションと動的セクションの両方を持つ柔軟性をもたらしてくれたし、それは重要でした。完全に静的なディレクトリでインタラクティブな機能がなければ、Astro のほうが構築が速く、実行コストが安いかもしれません。1 年後にまた聞いてください。

スクレイパーがディレクトリ全体をコピーするのを防ぐには?

正直なところ、完全には防げません。API ルートをレート制限して、フロントエンドで Cloudflare のボット管理を使い、構造化データの一部をローテーションしてスクレイプされたコピーがすぐに古くなるようにしています。ただ、公開されているディレクトリをクローンしたいと思う人がいれば、必ず方法を見つけるでしょう。競争優位性は技術的な難読化ではなく、データの鮮度と UX の品質なんです。

---

最後に

HostListは急成長していない。アフィリエイト手数料、数件の直接広告案件で収益を生み、当初ターゲットにした約600用語でそれなりなランキングを得ている。それで十分だ。学習プロジェクトで、同時に収益も生み出している。それが最高のプロジェクトの形だ。

大規模なプログラマティックSEOサイトをNext.jsで構築することを検討しているなら、率直なアドバイスはこうです。やるべきです。本当に優れたスタックです。ただし、必要だと思うよりも少なく構築し、時間があると思うよりもずっと丁寧に構築し、ページテンプレートを一つ書く前にデータアーキテクチャを整理してください。

技術の部分は簡単です。いつもそうです。

< BACK