Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Prerendering a page per device, and the cache key that has to agree

2026年10月29日に「Dev.to」が公開したITニュース「Prerendering a page per device, and the cache key that has to agree」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

Webサイトをデバイスごとに最適表示するため、Next.jsでページを事前生成し、高速提供する方法。一つのURLに対し、アクセス元のデバイスに合わせたページを裏側で切り替える仕組みだ。キャッシュの不整合を防ぐため、デバイス判定を統一することが重要となる。

ITニュース解説

ウェブサイトを開発する際、スマートフォン、タブレット、デスクトップPCなど、ユーザーが利用する様々なデバイスの画面サイズや操作方法に合わせて、ページの見た目や操作性を最適化することは非常に重要である。特に、企業のマーケティングページのように、訪問者ごとに内容が変わらない「静的」なページは、高速に表示されることが求められる。しかし、記事で紹介されているように、ページの一部(例えばフッターのレイアウト)が、スマートフォンではアコーディオン形式、デスクトップPCではグリッド形式のように、デバイスによって異なる表示をする必要がある場合がある。このようなページは、完全に静的とは言えず、デバイスごとに異なるHTMLを配信する必要が生じる。

従来のウェブ開発では、このような要件に対し、主に二つの方法が取られてきた。一つはCSSのメディアクエリを使う方法である。これは、一つのHTMLファイル内にデスクトップ用とモバイル用の両方のスタイルを記述し、ブラウザの画面サイズに応じて表示を切り替える方式だが、ユーザーが見ない方のCSSやHTMLまでダウンロードさせてしまうため、ページの読み込み速度が低下する可能性がある。もう一つはJavaScriptでデバイスを判断し、ページの読み込み後に内容を書き換える方法である。この方法は、ページが最初に表示されたときにレイアウトが一時的に崩れてから正しく表示される「FOUC」(Flash Of Unstyled Content)という現象を引き起こす可能性があり、ユーザー体験を損なう場合がある。

これらの問題を解決するために、ウェブページを事前にデバイスごとに生成し、サーバーがユーザーのデバイスに応じた最適なHTMLを直接提供する方法が考えられる。これを「プリレンダリング」(事前レンダリング、または静的サイト生成 SSG の一種)と呼ぶ。ページが事前にHTMLファイルとして用意されていれば、サーバーはリクエストに応じてそのファイルを渡すだけでよいため、表示速度が向上し、不要なダウンロードも発生しない。

Next.jsのようなモダンなウェブフレームワークは、このようなプリレンダリングの機能を提供している。Next.jsのApp Router環境では、ビルド時にページの内容をHTMLファイルとして事前に生成できる。生成されたHTMLファイルは、Next.jsが管理する内部キャッシュに保存され、同じURLへのリクエストがあった際には、再度レンダリングすることなく、このキャッシュから高速に提供される。しかし、このNext.jsのプリレンダリングの仕組みには一つの制約がある。デフォルトでは、ページのキャッシュキーはURLのパス(例: /terms-of-use)にのみ基づく。つまり、同じパスに対しては、常に同じコンテンツが提供される。もし、 /terms-of-use という一つのURLで、デバイスによって異なるHTMLを配信したい場合、このデフォルトのキャッシュメカニズムでは対応できない。URLパスだけでなく、デバイスタイプも考慮してキャッシュを使い分けられるようにする必要がある。

記事では、この課題を解決するために、Next.jsのApp RouterとNext.js 15の機能を利用した詳細なアプローチが紹介されている。その基本的な考え方は、一つの公開URL(例: /terms-of-use)に対して、内部的には「デスクトップ版の /desktop/terms-of-use 」と「モバイル版の /mobile/terms-of-use 」という二つの別々のHTMLファイルを事前に生成することである。そして、ユーザーからのリクエストがあった際に、サーバーの「ミドルウェア」という機能を使って、ユーザーのデバイスを判断し、適切な内部パスにリクエストを「リライト」(URLを書き換える)することで、デバイスに応じたHTMLを配信する。この際、ユーザーのブラウザに表示されるURLは常に公開URLのままであるため、SEOや共有のしやすさに影響はない。

この仕組みを実現するため、まずデバイスごとにコンテンツを分けたいページに export const isDeviceBasedSSG = true という特別なマーカーを付ける。次に、ビルドプロセスの中で実行されるスクリプトが、このマーカーが付けられたページを検出し、そのページに対応するデバイス別の「ラッパーページ」を自動生成する。例えば、app/terms-of-use/page.tsx という元のページに対して、スクリプトは app/(generated)/desktop/terms-of-use/page.tsx と app/(generated)/mobile/terms-of-use/page.tsx といったファイルを生成する。これらの生成されたラッパーページは非常にシンプルで、元のページファイルをインポートし、デバイスタイプ(例: "desktop" や "mobile")をpropsとして渡すだけである。記事では、/[device]/terms-of-use のようにURLパスを動的にするのではなく、desktop/terms-of-use や mobile/terms-of-use のような具体的なフォルダ名を使う方が、意図しないURLが有効になる問題を避けられ、安全だと説明している。

生成されたデバイス別ファイルは、ミドルウェアによって活用される。ミドルウェアは、サーバーでリクエストがアプリケーションのルートに到達する前に実行される機能である。ここで、ユーザーのHTTPヘッダー(ブラウザの種類やOSなどの情報を含む)を読み取り、そのデバイスタイプ(デスクトップかモバイルか)を判断する。そして、公開URLへのリクエスト(例: /terms-of-use)を、検出されたデバイスに対応する内部パス(例: /desktop/terms-of-use)に書き換える。これにより、Next.jsのルーティングシステムは、あたかも最初からデバイス別パスへのリクエストであったかのように処理し、適切なプリレンダリングされたHTMLを返すことができる。

このアプローチにおいて、最も注意が必要な点の一つが「キャッシュの鍵」である。ウェブサイトの速度向上には、CDN(Contents Delivery Network)などのキャッシュサービスが広く利用される。CDNは、ユーザーに近いサーバーにコンテンツを一時的に保存し、次回以降のリクエストに対して高速に配信する。このCDNも、どのようなリクエストに対してどのキャッシュされたコンテンツを返すかを判断するための「キャッシュキー」を持っている。もし、CDNがHTTPヘッダーの特定の情報(例: CF-Device-Type)を使ってデバイスを分類しキャッシュキーを生成する一方で、Next.jsアプリケーション内部のミドルウェアが別の情報(例: User-Agent)を使ってデバイスを分類した場合、両者の分類が食い違う可能性がある。例えば、CDNはデスクトップと判断したのに、アプリケーションはモバイルと判断するようなケースである。この場合、最初にアクセスしたユーザーのデバイスタイプに応じてキャッシュされたコンテンツが、後続の異なるデバイスタイプのユーザーにも提供されてしまい、誤ったレイアウトのページが表示されることになる。この問題を避けるため、記事では、デバイスを判断するロジックをアプリケーション全体で一つに集約し、CDNが提供するヘッダー情報を最優先で利用することを推奨している。これにより、CDNとNext.jsアプリケーションのキャッシュが同じ基準でデバイスを分類し、整合性を保つことができる。

Next.jsでページをプリレンダリングする場合、そのページやそのページが利用するレイアウトコンポーネント、NotFoundページなど、関連する全てのコンポーネントは、リクエストごとに内容が変わる可能性があるAPI(例: cookies(), headers(), searchParams)を読み込んではならないという制約がある。もしこれらを読み込んでしまうと、Next.jsはプリレンダリングを諦め、リクエストごとにサーバーサイドレンダリング(SSR)にフォールバックしてしまう。これはパフォーマンス低下の原因となるが、ビルド自体は成功するため、開発者がこの問題に気づきにくいという欠点がある。この「サイレントな失敗」を防ぐため、生成されるラッパーページには export const dynamic = 'error' が設定される。この設定により、もしプリレンダリングが期待されるページで意図せず動的なAPIが使われた場合、ビルド時にエラーが発生し、開発者は問題に早期に気づくことができる。

app/(generated)/desktop/terms-of-use のような、自動生成された内部パスは、あくまでミドルウェアを介してアクセスされることを想定している。もし何らかの理由で、ユーザーや検索エンジンのクローラーがこれらの内部パスに直接アクセスしてしまった場合、同じコンテンツが複数のURLで提供されることになり、SEO上の問題(重複コンテンツ)を引き起こす可能性がある。これを防ぐため、ミドルウェアでは、リクエストされたURLがデバイス別の内部パスであるかどうかをチェックする。もし内部パスへの直接アクセスであれば、/_not-found という存在しないページにリライトし、明示的に404エラーを返すことで、そのURLが公開されることを防ぐ。

この記事で紹介されているアプローチは、デバイスごとに最適化されたウェブページを高速に提供するための、Next.jsにおける高度なテクニックである。プリレンダリング、ミドルウェア、自動生成スクリプト、そしてキャッシュ戦略を組み合わせることで、ユーザー体験とパフォーマンスを向上させつつ、開発者が手作業で複数のページを管理する手間を削減している。この実装には、自動生成スクリプトがビルドプロセスに複雑さを加えたり、デバッグ時にエラーメッセージが生成されたファイルを示すため元のコードを探す手間が生じたりするといったトレードオフも存在する。しかし、多くのページでデバイス別最適化が必要な大規模なサイトでは、このような自動化のメリットがデメリットを上回ると判断される場合が多い。Next.jsのキャッシュの仕組みを深く理解し、CDNなど上位のキャッシュとの連携を考慮する必要があること、そして、プリレンダリングの制約(動的なAPIの不使用)をビルド時に検出し、サイレントな失敗を防ぐ工夫が重要である、という知見は、高性能なウェブアプリケーションを構築する上で非常に価値がある。

関連コンテンツ

関連IT用語

関連ITニュース