はじめに
前回(第 4 回「JSS と Next.js で組む headless 開発実践」)では、フロントエンドをどう組むかを実装目線で追った。本稿(第 5 回)は組み上げた構成を性能の観点から見直す。composable Sitecore の性能を語るときに見るべき場所は、オンプレ XP 時代とほぼ別物になる。IIS の output cache や SQL Server のインデックスではなく、Experience Edge と Next.js のレンダリング戦略、CDN のキャッシュ階層、GraphQL のクエリ設計、そして Real User Monitoring。それぞれの層で何を見て、何を回せばよいのかを順に整理する。
性能の地図
composable 構成のリクエスト経路はおおよそ次の順序になる。ユーザーのブラウザ → 自前ドメインの CDN(Vercel Edge Network / Azure Front Door / CloudFront)→ Next.js のレンダリング層(静的 HTML、ISR、Edge SSR のいずれか)→ Sitecore Experience Edge(GraphQL)→ XM Cloud Authoring。各段にキャッシュがあり、上位段でヒットすれば後ろは触らない。性能設計はこの「どの段でヒットさせるか」を決める作業に近い。
Next.js のレンダリングモードの選択
もっとも重要な判断は、各ページをどのモードで配信するかだ。コーポレートサイトの製品紹介ページや会社情報のように更新頻度が低いコンテンツは SSG(Static Site Generation)。記事一覧や商品カタログのように分単位で内容が変わるが秒単位ではないものは ISR(Incremental Static Regeneration)。検索結果ページや個人ダッシュボードのように毎リクエスト動的に組む必要があるものは Edge SSR。
SSG は CDN ヒットでマイクロ秒級、ISR も初回再生成後はほぼ CDN ヒットで返る。Edge SSR は GraphQL を都度叩くが、Edge 側にレスポンスキャッシュを噛ませれば実効的にほぼ静的に近づけられる。「全部 SSG」が理想ではなく、コンテンツ性質ごとに切る発想が要る。
ISR と Edge からの webhook
XM Cloud で公開が走ると Experience Edge にコンテンツが push される。フロントエンド側はこれを受けて、関係する Next.js ページを revalidatePath で再生成する。これにより、編集者が公開ボタンを押してから利用者に届くまでの遅延を、CDN の最大 TTL ではなく Edge からの webhook 遅延(実測で 1–3 秒)に抑えられる。
// app/api/revalidate/route.ts
import { revalidatePath } from "next/cache";
export async function POST(req: Request) {
const body = await req.json();
for (const change of body.changes) {
revalidatePath(change.path); // 単ページ
// 関連一覧も明示的に
if (change.template === "Article") revalidatePath("/articles");
}
return Response.json({ ok: true });
}
注意点は、関連するインデックスページや一覧ページの再生成を忘れないことだ。Experience Edge は変更があった item のパスしか教えてくれないので、テンプレ → 影響先パスの依存関係はフロント側の責務になる。
GraphQL クエリの設計
Experience Edge への GraphQL は、過大なクエリで叩くとそれ自体が遅延要因になる。レンダリングに本当に必要なフィールドだけを取る、フラグメントを使って再利用する、リスト系は persisted query として識別子で叩く、の 3 点が基本だ。persisted query は事前に Edge に登録したクエリを queryId で呼び出す仕組みで、リクエストの URL 長が固定化され、Edge / CDN レイヤでキャッシュキーが安定する。
// 開発時はそのまま投げ、ビルド時に persisted query 化
const ARTICLE_LIST_QUERY = gql`
query ArticleList($language: String!, $first: Int!) {
site(name: "withnext") {
items(template: "Article", first: $first, language: $language) {
nodes {
path
title: field(name: "Title") { value }
excerpt: field(name: "Excerpt") { value }
eyecatch: field(name: "Eyecatch") { ... on ImageField { src alt } }
publishedAt: field(name: "PublishedAt") { value }
}
}
}
}`;
CDN キャッシュの階層
Vercel / Azure Front Door / CloudFront など、選んだホスティング先の CDN を「Edge 配信を二段で支える層」として使う。静的 HTML(SSG)はそのまま CDN にキャッシュ。ISR の再生成後の HTML も CDN がキャッシュ。Edge SSR のレスポンスも、可能なら CDN レイヤで短い TTL(10〜60 秒)の cache を許可する。重要なのは Cache-Control ヘッダの設計で、長期キャッシュしてよいアセットと、ISR が触れる可能性のある HTML を、CDN 側のルールで分けることだ。
画像は Experience Edge が提供するメディア URL を CDN にプロキシする形が標準で、Edge 側で自動的に最適化フォーマット(WebP/AVIF)を返してくれる。
地理的近接 — Edge の話
Experience Edge は global に分散したエッジから GraphQL を返す。ユーザーが日本からアクセスしているなら、東京リージョンの Edge から数十 ms で返ってくる。Next.js を Vercel Edge Network や Azure Static Web Apps の global deployment に乗せておけば、フロント側のレンダリングも近接で済む。「東京リージョンの DB を一段で叩く」というオンプレ時代の発想から、「ユーザーに最も近い Edge で完結する」設計に置き換わる。
Real User Monitoring と Edge ログ
性能は実ユーザーの体感で評価する。Vercel Speed Insights、Azure Application Insights、Datadog RUM、Cloudflare Web Analytics のいずれかをフロントに入れ、Core Web Vitals(LCP / INP / CLS)を継続観測する。改善対象は LCP が大半で、画像の遅延、CSS の blocking、初期 JS バンドルが主犯になる。
Experience Edge と Next.js の API ルートのログは、別系統で収集する。Edge の cache hit/miss、GraphQL のクエリ別レイテンシ、ISR の再生成回数。ここがダッシュボード化されていると、「ある記事だけ ISR が分単位で再生成されている」「特定の GraphQL が cold cache を引きやすい」といった偏りが早く見つかる。
よくある詰まりどころ
新規導入で性能が出ない場面は、ほぼ次のいずれかに該当する。第一に、すべてのページを Edge SSR にしてしまい、Edge / CDN のキャッシュが効いていないケース。本来 SSG/ISR で済むコンテンツが多数派なので、ルートごとに見直す。第二に、GraphQL を画面の上から下まで一発で取る巨大クエリにしているケース。レンダリング単位に分割すると個別キャッシュが効く。第三に、CDN の Cache-Control を Next.js 側のデフォルト任せにし、HTML が短すぎる TTL で出ているケース。明示的に s-maxage を設計する。
次回
最終回(第 6 回)は 「コンポーザブル Sitecore 導入のいま — 設計判断と落とし穴」。ここまでの 5 本で扱った composable 構成を、リテール omnichannel、B2B 製造業ポータル、メディア大規模配信の 3 つの架空シナリオに当てはめ、設計判断と落とし穴を整理する。
