Core Media Lab記事一覧
開発・インフラ

【実録】Astro × microCMSで古い記事がトップに飛ぶ・404になる原因と解決法!10件制限を突破する全件取得(do...while)の実装手順

【実録】Astro × microCMSで古い記事がトップに飛ぶ・404になる原因と解決法!10件制限を突破する全件取得(do...while)の実装手順

AstroとmicroCMS、Cloudflare Pagesを組み合わせてWebメディアを運営していると、「新しく公開した記事は正常に開けるのに、過去に公開した古い記事をクリックすると404エラーになる、あるいはURLはそのままでトップページへ強制的に飛ばされてしまう」という不可解な現象に直面することがあります。

当メディア(Core Media Lab)でも、記事数が20本・30本と増えたタイミングで、過去の記事(例:/blog/dify-n8n-webhook-automation-guide など)が突如として表示されなくなるトラブルが実際に発生しました。Cloudflare Pagesのデプロイステータスは「Success(成功)」となっており、エラーログも一切出ていないため、一見すると原因が分かりにくい厄介な現象です。

結論からお伝えすると、この現象の根本原因はmicroCMSのリスト形式APIにおける「デフォルト取得件数(limit: 10)」の仕様にあります。ビルド時に最新10件の記事データしか取得されておらず、11件目以降の古い記事の静的HTMLファイルがそもそも生成されていなかったことが原因です。

この記事では、実際に当サイトで発生したビルドログの検証結果をもとに、現象の切り分け手順から、将来記事が何百件・何千件に増えても一切取りこぼさない「do...whileループによる全件自動取得(ページネーション処理)」の実装コードまでを分かりやすく解説します。

1. 発生した現象:最新記事は開くのに、過去の記事がトップページに飛ばされる

まずは、実際に当メディアの運営中に発生した現象と、どこに異常があったのかを整理します。同じ症状が出ている方は、ご自身の環境と照らし合わせてみてください。

実際に起きた症状とCloudflare Pagesの状態

現象に気づいたきっかけは、サイト内の記事リンクから過去記事へアクセスした際、記事本文が表示されずトップページ(一覧画面)が表示されてしまったことでした。その際、環境は以下のような状態でした。

  • 直近で公開した最新記事:問題なく個別記事ページが表示される。
  • 過去に公開した古い記事:クリックするとページが表示されず、トップページへ飛ばされる(または404になる)。
  • Cloudflare Pagesのデプロイ状況:管理画面の「Deployments」タブを確認しても、直近のビルドはすべて緑色のチェックマーク(Success)で正常終了している。
  • 環境変数とビルド設定:Cloudflare Pagesの「Settings」において、ビルド出力先(dist)や環境変数(MICROCMS_API_KEY、MICROCMS_SERVICE_DOMAIN)は正しく設定されている。

ビルドエラーが一切出ていないにもかかわらず特定のページだけが存在しない扱いになるため、当初はCloudflare側のリダイレクト設定やキャッシュの問題を疑いました。しかし、手元のローカル環境でビルドを実行したことで、真の原因が判明しました。

ローカルビルド(npm run build)のログで判明した「生成漏れ」

Cloudflare上の問題なのか、Astroのコード側の問題なのかを切り分けるため、ローカル環境のターミナルで本番ビルドコマンド(npm run build)を実行し、静的ルート(generating static routes)の出力ログを確認しました。

> astro build
...
generating static routes
23:32:50   ├─ /about/index.html (+22ms)
23:32:50   ├─ /blog/vps-ubuntu-docker-security-hardening-guide/index.html (+253ms)
23:32:51   ├─ /blog/astro-cloudflare-pages-build-error-fix/index.html (+235ms)
23:32:51   ├─ /blog/n8n-twitter-x-auto-post-microcms/index.html (+204ms)
23:32:51   ├─ /blog/astro-microcms-image-optimization-pagespeed/index.html (+202ms)
23:32:51   ├─ /blog/gas-dify-seo-rewrite-automation/index.html (+210ms)
23:32:51   ├─ /blog/dify-vps-502-bad-gateway-fix/index.html (+198ms)
...
23:32:54 [build] 20 page(s) built in 10.96s
23:32:54 [build] Complete!

ビルド自体はエラーなく完了していましたが、出力されたページ数(20 page(s) built)と生成されたHTMLのパスを確認すると、microCMSに入稿して公開状態になっているはずの20本以上の記事のうち、最新の10件分しかHTMLが生成されていなかったのです。

AstroのようなSSG(静的サイトジェネレーター)では、ビルド時に生成されなかったURLへアクセスするとファイルが存在しないため404エラーになるか、ホスティング環境(Cloudflare Pages等)のフォールバック挙動によってトップページへ転送されてしまいます。なお、初期構築全体の流れやトラブルシューティングについては、Astro × microCMS × Cloudflare Pagesで爆速メディアを立ち上げる完全解説記事でも詳しく紹介しています。

2. 根本原因はmicroCMS APIの「デフォルト取得件数(limit: 10)」の仕様

なぜ公開済みの記事がすべて取得されず、最新10件で止まってしまったのでしょうか。その理由は、動的ルーティングを担う src/pages/blog/[id].astro 内のデータ取得コードと、microCMSのAPI仕様にありました。

修正前の [id].astro のコードと問題点

トラブル発生時、個別記事のパスを生成する getStaticPaths() 関数は以下のように記述されていました。

// ❌ 問題があった修正前のコード(src/pages/blog/[id].astro)
export async function getStaticPaths() {
  const client = createClient({
    serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
    apiKey: import.meta.env.MICROCMS_API_KEY,
  });

  // queries(取得条件)を指定せずに getList を呼び出している
  const data = await client.getList({
    endpoint: "blogs",
  });

  return data.contents.map((content) => ({
    params: { id: content.id },
  }));
}

microcms-js-sdk の client.getList() メソッドを使ってコンテンツ一覧を取得する際、queries パラメータで limit(取得件数)を明示的に指定しない場合、microCMSのAPI仕様によりデフォルトで「最大10件(limit: 10)」までしかデータが返却されません。

つまり、サイト立ち上げ初期の記事数が1〜10件までの間は問題が表面化せず、11記事目を公開した瞬間から、古い記事が押し出される形でビルド対象から漏れ続けていたというわけです。microCMSの基本的なAPI連携やヘッドレスCMSの仕組みについては、microCMSで個人メディアをAPI・自動化で省力化する導入ガイドもあわせて参考にしてください。

「limit: 100」の指定だけでは将来的に不十分な理由

この問題に対する応急処置として、以下のように queries: { limit: 100 } を追加する方法がよく紹介されています。

// ⚠️ 一時的な対処法(記事数が100件を超えると再発する)
const data = await client.getList({
  endpoint: "blogs",
  queries: { limit: 100 },
});

確かに記事数が数十本のうちはこれで解決しますが、メディアを継続的に運営して記事数が100件を超えた場合、あるいはmicroCMS側の1リクエストあたりの取得上限に達した段階で、再び古い記事が消えるという同じトラブルが再発してしまいます。

今後の運営で記事が100件、200件、1,000件と増えていってもコードを一切書き換える必要がないよう、次章で解説する「全件取得の自動ループ処理(ページネーション)」を最初から組み込んでおくのが最も確実な解決策です。

3. 解決策:do...whileループでmicroCMSの記事を全件自動取得する実装手順

記事数が今後100件、500件、1,000件と増えていっても一切の修正なしで全記事をビルドできるようにするため、offset(取得開始位置)と limit(1回あたりの取得件数)を組み合わせたページネーションループ(do...while文)を実装します。

全件取得ループの仕組み

microCMSのリスト形式APIは、レスポンスの中に必ず totalCount(公開されている全記事の総数)を返してくれます。この仕様を利用し、以下のような流れで全件を取得します。

  1. 1回のリクエストで最大100件(limit: 100)ずつ記事を取得し、配列(allContents)に追加する。
  2. 次回取得時の開始位置(offset)に 100 を加算する。
  3. これまでに取得した記事の合計数(allContents.length)が、全体の総数(totalCount)に達するまでループを繰り返す。

このロジックを組んでおけば、記事が30件のときは1回のAPIリクエストで終了し、150件になれば自動的に2回ループして150件すべてを取得してくれます。

コピペで使える src/pages/blog/[id].astro の完全版コード

実際に当メディアで修正・適用し、全記事のビルド成功を確認した src/pages/blog/[id].astro のコード全文は以下の通りです。部分的なコピペによる構文エラーを防ぐため、getStaticPaths() 周辺だけでなくファイルの先頭から構造を確認して反映してください。

---
import { createClient } from "microcms-js-sdk";
import "../../styles/global.css";

const client = createClient({
  serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
  apiKey: import.meta.env.MICROCMS_API_KEY,
});

export async function getStaticPaths() {
  const client = createClient({
    serviceDomain: import.meta.env.MICROCMS_SERVICE_DOMAIN,
    apiKey: import.meta.env.MICROCMS_API_KEY,
  });

  // 100件ずつ自動でループしてすべての記事を全件取得する実装
  let allContents: any[] = [];
  let offset = 0;
  const limit = 100;
  let totalCount = 0;

  do {
    const data = await client.getList({
      endpoint: "blogs",
      queries: { offset, limit },
    });
    allContents = [...allContents, ...data.contents];
    totalCount = data.totalCount;
    offset += limit;
  } while (allContents.length < totalCount);

  return allContents.map((content) => ({
    params: { id: content.id },
  }));
}

const { id } = Astro.params;

const blog = await client.getListDetail({
  endpoint: "blogs",
  contentId: id,
});

// メタディスクリプション用の要約文
const plainDescription = blog.content
  ? blog.content.replace(/<[^>]+>/g, "").slice(0, 120) + "..."
  : "テックブログの記事詳細ページです。";

const siteUrl = "https://coremedialab.com";
const pageUrl = `${siteUrl}/blog/${blog.id}`;
const ogImage = blog.eyecatch ? blog.eyecatch.url : `${siteUrl}/favicon.svg`;
---

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>{blog.title} | Core Media Lab</title>
    <meta name="description" content={plainDescription} />
    <!-- OGP・GA4タグ等はサイトの環境に合わせて配置 -->
  </head>
  <body>
    <main>
      <article>
        <h1>{blog.title}</h1>
        <div set:html={blog.content} />
      </article>
    </main>
  </body>
</html>

※上記のHTMLパート(<!doctype html> 以降)は、ご自身のサイトのTailwind CSSクラスやレイアウト構造に合わせて維持してください。

4. 実装時によくあるつまずきと周辺ファイルの点検ポイント

コードを差し替える際、実際に私たちが直面したエラーや、あわせて確認しておくべきプロジェクト内の見落としやすいポイントを3つ共有します。

① コピペ時の「[CompilerError] Unexpected token」に注意

コードを修正した直後に npm run build を実行した際、以下のようなコンパイルエラーが発生することがあります。

[ERROR] [vite] ✗ Build failed in 423ms
[CompilerError] Unexpected token
  Location:
    C:/Users/.../my-tech-media/src/pages/blog/[id].astro:2:2

このエラーは、関数部分だけをコピー&ペーストした際に、Astroファイル先頭のフロントマター記号(---)が消えてしまった、あるいは閉じカッコ(})の数や全角スペースの混入によって構文が崩れたことが原因です。修正時はファイル先頭が正しく ---(1行目)から始まっているかを必ず確認してください。

② 記事一覧ページ(トップページ等)の取得漏れもあわせて確認する

個別記事ページ([id].astro)の生成漏れを直しても、トップページ(src/pages/index.astro)やブログ一覧ページ(src/pages/blog/index.astro)側でmicroCMSから記事一覧を取得している箇所がデフォルト(limit: 10)のままだと、一覧画面に古い記事が表示されません。

また、Astroの初期ブログテンプレートをベースに構築した場合、以下のようにローカルのMarkdownコレクション(getCollection('blog'))を読み込む初期ファイルが残っているケースがあります。

  • src/pages/blog/index.astro(初期テンプレートの first-post 等を読み込んでいる)
  • src/pages/blog/[...slug].astro(ローカルMarkdown用の動的ルーティング)

microCMSへ完全に移行している場合は、一覧ページ側でも同様に client.getList で必要な件数(または全件)を取得するように統一し、使っていない初期テンプレートのダミー記事(first-post、second-post 等)や不要なルーティングファイルは整理しておくと、ビルド時間の短縮とルーティング競合の防止につながります。

③ バックアップ用ファイル(.zip等)の誤コミットに注意

ファイルを修正する際、念のため src/pages/blog.zip のようにフォルダ内で圧縮ファイルやバックアップファイルを作成することがあります。そのまま git add . を実行すると、不要な圧縮ファイルまでGitHubリポジトリへプッシュされてしまいます。

作業用の一時ファイルはプロジェクト外に置くか、.gitignore に *.zip などを追加してリポジトリをクリーンに保つのがおすすめです。

5. 修正後のビルド検証とCloudflare Pagesへのデプロイ反映

コードの修正が完了したら、手元のローカル環境でビルドを実行し、すべての記事が漏れなく生成されるかを確認します。

1. ローカルでビルド(npm run build)を実行して生成ページ数を確認

ターミナルで再度ビルドを実行します。

npm run build

修正前は 20 page(s) built だった出力結果が、修正後は以下のように 39 page(s) built in 7.69s へと増加し、microCMS上のすべての過去記事がHTMLとして正常に出力されたことが確認できました。

23:50:40 [build] ✓ Completed in 7.29s.
23:50:40 [@astrojs/sitemap] `sitemap-index.xml` created at `dist`
23:50:40 [build] 39 page(s) built in 7.69s
23:50:40 [build] Complete!

2. GitHubへプッシュしてCloudflare Pagesへ自動デプロイ

ローカルでのビルド成功を確認したら、以下のコマンドでGitHubへ変更をプッシュします。

# 1. 変更されたファイルをステージングに追加
git add .

# 2. コミットメッセージを記録
git commit -m "fix: microCMSの記事全件取得ループ(pagination)を実装"

# 3. GitHubへプッシュ(Cloudflare Pagesの自動デプロイが起動)
git push

プッシュ完了後、1〜2分ほど待ってからCloudflare Pagesのデプロイ完了を確認し、これまでトップページに飛ばされていた過去記事のURLへ再度アクセスしてみてください。すべての個別記事がエラーなく瞬時に表示されるようになります。なお、GitHubとCloudflare Pagesを連携した自動デプロイ(CI/CD)の仕組みについては、GitHub × Cloudflare Pagesで実現する完全無料・自動デプロイ環境の作り方で詳しく解説しています。

まとめ:記事が10件を超える前に「全件取得ループ」を実装しておこう

今回のトラブルと解決のポイントを整理します。

  • 原因:microCMSの getList APIは、limit を指定しないとデフォルトで最新10件までしか取得しない仕様になっている。
  • 症状:記事数が10件を超えると、11件目以降の古い記事がAstroの静的ビルド(SSG)から漏れ、アクセス時に404エラーやトップページへの強制遷移が発生する。
  • 解決策:単なる limit: 100 の固定指定ではなく、do...while と offset を使った全件自動取得ループを getStaticPaths() に実装することで、何百記事に増えてもメンテナンスフリーで全ページを生成できる。

Astro × microCMSは非常に高速で管理しやすい構成ですが、APIの取得上限のような「初期構築時には気づかず、運用して記事が増えてから初めて発覚する落とし穴」が存在します。これからメディアをスケールさせていく方や、過去記事のリンク切れにお困りの方は、ぜひ今回紹介した全件取得ループを導入してみてください。

あわせて読みたい:メディア運営の自動化・インフラ構築ガイド

サイトの記事数が増えてきたら、n8nやDify、Pythonスクリプトを24時間稼働させて「記事作成・SNS投稿・データ分析」を自動化する専用環境の構築もおすすめです。コストと安定性に優れた開発・自動化向けVPSの選び方は、【2026年最新】開発・自動化におすすめのVPS 3選を徹底比較!コストとスペックで選ぶ最適解で詳しく解説しています。


Ys
この記事を書いた人
Ys(ワイズ)/ Webエンジニア・自動化メディア運営者

「手作業を極限まで減らし、維持費0円でスケールする」をテーマにWebメディア・業務自動化システムを個人開発。WordPressから Astro × microCMS × Cloudflare Pages へ完全移行し、Googleスプレッドシート・GAS・AIを活用した完全自動入稿パイプラインを構築・運用中。机上の空論ではなく、自ら手を動かして直面したトラブルシューティングと現場の一次情報を発信しています。