2026年10月8日にJamstack公式説明とNext.jsのStatic export・Incremental Static Regenerationガイドの必要範囲を再確認した。Jamstackはpre-renderingとdecouplingを軸にビルド時と実行時を分ける考え方で、static exportは出力ファイルを配信し要求時サーバー機能やISRに制約がある。Cache Componentsなしの公式ISR例では期限後の要求で再生成が起こり、古い内容が返る場合もあるため、定刻更新・性能・安全・費用を一律に保証しない。
30秒で要点
Jamstackは、事前に生成できる画面を配信用のファイルとして用意し、データや業務処理の機能を分離するWebサイトの構成です。特定のフレームワーク名でも、無料で速く安全になる仕組みでもありません。
- 誰にでも同じ内容を見せるページは、事前生成と相性がよい候補です。
- ログイン、最新在庫、送信内容の保存などは、実行時のAPIが担当します。
- 更新頻度、反映までの許容時間、運用費を基に、生成と実行の境界を決めます。
この記事では、ブログや企業サイトを出発点に、会員機能や検索を追加する場合も含めて判断材料を整理します。Jamstack公式サイトの定義では、事前生成と機能の分離が中心に置かれています。
ビルド時、配信時、利用時を分けて考える
JavaScript、API、Markupという名前の由来だけで構成を決めると、どこで処理が動くのかを見落とします。大切なのは、ページを作る時点と、利用者ごとに処理する時点の区別です。
ビルドでは、記事や商品説明などの入力からHTML、CSS、画像などを作ります。配信では、それらを静的ホストやCDNから返します。利用者がログインしたり検索したりしたときは、必要に応じてJavaScriptからAPIへ接続します。すべてのページでAPI通信を行う必要はありません。
表が収まらない場合は、横にスクロールできます
| 内容 | 作る・確認する時点の候補 | 注意すること |
|---|---|---|
| 企業情報、記事本文 | ビルド時 | 修正から公開までの時間、古いファイルの残存 |
| 公開商品説明 | ビルド時または再生成時 | 説明と、購入時の価格・在庫の責任を分ける |
| 会員の注文履歴 | 認証後のAPI要求時 | サーバー側の認可と個人別キャッシュ |
| 問い合わせ送信 | APIへ送信した時点 | 保存結果、再送、スパム、個人情報の保管 |
| サイト内検索 | 公開索引の生成時または検索API要求時 | 下書き・非公開文書の混入、索引の更新遅延 |
この分類は設計例です。同じサイトでもページやデータごとに異なる方式を選べます。生成済みの価格表示を、そのまま購入を確定する根拠にはしないでください。
小さなHTML生成を、ローカルで確かめる
まずはフレームワークなしで、入力と公開物の関係を確認します。以下は公開する架空の記事だけを含む例です。Node.js 24.18.0で検証しています。外部パッケージやWebサーバーは不要です。
空の作業ディレクトリへpages.jsonを保存します。
[
{ "slug": "about", "title": "私たちについて", "body": "小さな公開ページです。" },
{ "slug": "support", "title": "サポート & ご案内", "body": "<script>という文字も文章として表示します。" }
]
同じ場所へbuild.mjsを保存します。この例は出力先のoutがまだ存在しない状態で実行してください。
import { readFile, mkdir, writeFile } from 'node:fs/promises';
const pages = JSON.parse(await readFile('pages.json', 'utf8'));
const seen = new Set();
if (!Array.isArray(pages)) throw new Error('Expected an array');
for (const page of pages) {
if (!page || typeof page.slug !== 'string' ||
!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(page.slug) ||
typeof page.title !== 'string' || typeof page.body !== 'string' ||
seen.has(page.slug)) {
throw new Error('Invalid or duplicate page');
}
seen.add(page.slug);
}
const escapeHTML = (value) => value.replace(/[&<>"']/g, (char) => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
})[char]);
await mkdir('out');
for (const page of pages) {
const title = escapeHTML(page.title);
const body = escapeHTML(page.body);
const html = `<!doctype html>
<html lang="ja"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>${title}</title></head>
<body><main><h1>${title}</h1><p>${body}</p></main></body></html>`;
await writeFile(`out/${page.slug}.html`, html, 'utf8');
}
console.log(`Generated ${pages.length} pages`);
node build.mjsを実行すると、out/about.htmlとout/support.htmlができます。これはファイル生成までの例で、公開は行いません。テキストとして扱う入力をエスケープしているため、本文中の<script>はHTML要素になりません。自由なHTMLを受け入れるCMSには、別の無害化方針が必要です。
入力を書き換えても、すでに作ったHTMLは変わりません。新しい空の作業ディレクトリで同じビルドを行い、出力差分を比べると、再生成の意味を確かめられます。outが存在すれば例は停止します。古い記事の残存を避ける本番ビルドでは、毎回新しい出力領域を作り、全体の検証後に公開先を切り替える方法が候補になります。
入力のslugを../privateへ変える、または同じslugを2件にする確認もできます。出力前にエラーになることを確かめてください。この例の検証は限定的であり、CMS全体の安全性や配信設定まで保証するものではありません。
CMS更新は、保存・生成・公開をつなぐ作業
ヘッドレスCMSは、編集画面やコンテンツ管理を配信画面から分離する選択肢です。CMSへ保存できたことと、利用者に新しい内容が見えることは同じではありません。
更新経路は、編集・承認、ビルドの開始、入力の取得、生成、検証、公開切替、キャッシュ反映という段階に分けて追います。ビルド通知を受け取る入口には認証と重複処理への対策が必要です。短時間に大量の編集があれば、変更をまとめて生成する方が適切な場合もあります。
プレビューは公開版と別の権限で扱います。下書きを含めて生成したHTMLや検索索引を公開配信先へ置けば、画面にリンクがなくても取得される可能性があります。ブラウザへ配るJavaScript、HTML、JSONは公開物として点検してください。ビルド時に使用するCMSの秘密鍵を、出力へ埋め込んではいけません。
更新失敗時は、直前の公開版を維持する、編集者へ通知する、修正後に再実行するといった手順を決めます。削除や公開停止には、旧ファイル、CDN、検索索引から取り除く確認も必要です。
静的出力、再生成、要求時レンダリングの違い
フレームワークを使う場合も、名前だけでホストを選ばず、利用する実行機能を確認します。Next.jsのstatic exportは生成済みファイルを出力します。要求ごとのサーバー処理を必要とする機能やISRは、その構成では利用できません。
一方、ISRの公式ガイドにあるCache Componentsを使わない構成の時間指定例では、期限を過ぎた後の要求が再生成のきっかけになります。その要求には古い内容が返り、裏で生成した後に新しい内容へ切り替わる場合があります。「600秒を指定すれば600秒ごとに必ず更新される」という時刻保証ではありません。
表が収まらない場合は、横にスクロールできます
| 方式 | 更新のきっかけ | 選ぶ際の確認点 |
|---|---|---|
| 静的出力 | ビルドと公開切替 | ページ数、ビルド時間、公開物の整合性 |
| ISRなどの再生成 | 設定した期限後の要求や明示的な無効化 | 実行環境の対応、古い内容を返す条件、失敗時の挙動 |
| 要求時レンダリング | 利用者からの要求 | 処理時間、稼働費、障害、個人別キャッシュ |
| ブラウザからAPI取得 | 表示や操作に応じた通信 | 読み込み中・失敗時の画面、認可、API費用 |
これらを組み合わせることもできます。ただし、対応するホスト、アダプター、フレームワークの設定は変わり得ます。静的ファイルを配れることから、すべてのサーバー機能も動くと推測しないでください。
動的機能を追加するときの責任
認証と会員ページ
ログイン画面を表示できることと、データを守れることは別です。APIは毎回必要な権限を確認し、他人のIDへ変更した要求を拒否します。公開HTMLに会員データを含めず、個人別応答がCDNで共有されない設定を確認します。詳細はWeb認証の基本を参照してください。
フォームと注文
送信ボタンを押した直後に「完了」と表示せず、サーバー側で受け付けた結果を確認します。通信が切れた場合には、未保存なのか、保存済みだが応答が届かなかったのか分からないことがあります。重要な操作には操作IDや受付番号を用い、再送で二重注文を作らない処理が必要です。
サーバーレス関数を使っても、入力検証、認可、保管期限、再試行、監視の責任は残ります。サーバーレスの運用境界と合わせて検討してください。
検索と外部API
小さな公開サイトなら、生成した公開記事の索引をブラウザへ配る方法があります。索引が大きければ配信量や端末の処理が増えます。検索サービスを使う場合は、検索用に公開できるキーと管理用の秘密鍵を区別し、閲覧権限を検索結果にも反映します。
APIが止まっても記事本文は読める構成にできますが、検索や注文まで使える保証にはなりません。機能ごとに、停止時の表示、問い合わせ先、復旧後の確認を決めます。
導入判断は、公開までの時間と総費用で行う
事前生成は要求時の処理を減らす手段ですが、画像やJavaScriptが重ければページは遅くなります。CDNを使っても、端末の処理やAPI応答の遅れは残ります。採用前後で同じ内容・端末・通信条件を比べてください。
費用は配信だけでなく、ビルド回数と時間、CMS、API、ストレージ、転送量、プレビュー、ログ、運用担当の作業を含めます。無料枠を超えたときの単価や制限は、契約先の現行条件を確認します。一般的な削減率や攻撃件数の減少率を、そのサイトの成果として見積もらないことが大切です。
最初は更新頻度が低い公開ページを1種類選び、編集から公開までを試します。正常系だけでなく、入力の誤り、ビルド失敗、API停止、公開の取り消しも確認します。公開物を把握でき、許容する鮮度と運用体制に合うなら、段階的に対象を広げられます。