SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Remixを通じてWebを学ぶ

【検証】RemixとCloudflare Pagesを組み合わせるとどうなる?

Remixを通じてWebを学ぶ 第11回

  • X ポスト
  • このエントリーをはてなブックマークに追加

loaderにサムネイル画像を返すAPIを追加する

 では次に、サムネイル画像を生成する処理を作ってみましょう。今回は、vercel/ogというライブラリの力を借りて、画像生成をしてみようと思います。このライブラリは本来、VercelのEdge Functionsという環境で使うために作られたものですが、Cloudflare PagesのFunctions環境でも使えるラッパーがCloudflareから提供されています。

 vercel/ogの画期的な点は、JSX記法で記述したHTMLを画像として出力できるところです。実際の動作を確認してみましょう。まずは、Cloudflare版のvercel/ogをインストールします(リスト4)。

[リスト4]Pages向けのvercel/ogをインストールする
npm install @cloudflare/pages-plugin-vercel-og

 次に、サムネイル画像を生成するための関数を作成します。/top20/:記事ID/ogp.pngのようなパスにしたいので、ルートファイルの名前はtop20.$id.ogp[.]png.jsxにします。[]で囲んだ部分はパスの区切りではなく文字列としてそのまま扱われるため、ogp/pngではなくogp.pngがパスとして認識されます。

 また、このファイルを作ると、元々あったtop20.$id.jsxがレイアウトファイルとして認識されてしまうので、ページであることを明示するためにtop20.$id._index.jsxにリネームします(図10)。

図10:ファイルの追加と命名変更を行う
図10:ファイルの追加と命名変更を行う

 それでは、実際の画像生成の処理を書いてみましょう(リスト5)。

[リスト5]app/routes/top20.$id.ogp[.
png.jsx]
import { getItem } from '~/utils/hackerNews.server';

export const loader = async ({ params }) => {
  // (1) APIからデータを取得する
  const { id } = params;
  const item = await getItem(id);
  // (2) vercel/ogのモジュールをインポートする
  const { ImageResponse } =
    await import('@cloudflare/pages-plugin-vercel-og/api');
  // (3) JSXでサムネイル画像のレイアウトを定義する
  return new ImageResponse(
    <div
      style={{
        display: "flex", width: '100%', height: '100%',
        alignItems: 'center', justifyContent: 'center', fontSize: 40,
      }}
    >
      {item.title}{/* (4) テキストを埋め込む */}
    </div>,
    // (5) 画像のサイズを指定する
    { width: 1200, height: 630 },
  );
};

 (1)でAPIからデータを取得するのは、通常の記事ページと同じ処理です。(2)では、vercel/ogのモジュールを動的インポートしています。通常のインポートを行うとローカル環境でビルドエラーになるためです。実環境でしか動かないので、動作確認も実環境で行なってください。

 (3)では、どんな画像にするかを設定しています。ImageResponseの第一引数にはJSXでHTMLを記述しました。(4)で記事のタイトルを埋め込んでいます。(5)の第二引数には、オプションとしてサムネイル画像のサイズを定義しています。このファイルをCloudflare Pagesにデプロイして、実際に画像が生成されるか確認してみましょう。デプロイが完了したら、ブラウザでhttps://[サイトのURL]/top20/41371106/ogp.pngにアクセスします(図11)。

図11:サムネイル画像が表示された
図11:サムネイル画像が表示された

 画像が生成されていることが確認できました。JSXで指定した通り、記事のタイトルが画像の中央にレイアウトされています。

キャッシュの設定を行う

 さて、画像の生成はできましたが、キャッシュの設定はどうなっているでしょうか。ドキュメントによれば、CloudflareはデフォルトでPNG拡張子のレスポンスをキャッシュすることになっています。開発者ツールを開いたまま/top20/41371106/ogp.pngにアクセスして、どんなキャッシュ設定になっているかみてみましょう(図12)。

図12:サムネイル画像のキャッシュ状況
図12:サムネイル画像のキャッシュ状況

 どうやら、Cache-Control: public, max-age=31536000, immutable, no-transformが設定されているようです。新鮮な期間は1年間、再検証は行わない、変換は行わないという設定ですね。これだとタイトルが変わったときに、画像が更新されなくて困ってしまうので、キャッシュの設定を変更してみましょう。

 ImageResponseの第二引数に、ヘッダーを指定するオプションを追加することで、キャッシュの設定を変更できます(リスト6)。

[リスト6]app/routes/top20.$id.ogp[.
png.jsx]
// (略)
{
  width: 1200, height: 630,
  headers: {
    'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=60',
  },
}
// (略)

 このように設定することで、画像のキャッシュが60秒間有効で、60秒間は古いキャッシュを返しつつ、オリジンサーバーに再検証を要求するようになります。これで、画像の更新が反映されやすくなるはずです。次は実際の動きを見てみましょう。CDNキャッシュがない状態でアクセスすると、まずはMISS状態になります(図13)。

図13:キャッシュがないのでMISS状態
図13:キャッシュがないのでMISS状態

 Cache-ControlはPagesがつけたデフォルトのヘッダーが混ざっておりmax-ages-maxageが併記されていますが、併記した場合はs-maxageが優先されるため、60秒間はキャッシュが新鮮な状態で返されます。再度アクセスするとHIT状態になります(図14)。

図14:キャッシュがあるのでHIT状態
図14:キャッシュがあるのでHIT状態

 さらにその後、60秒以上経過してからアクセスすると、新鮮ではなくなっているのでEXPIREDになります(図15)。

図15:キャッシュが古くなっているのでEXPIRED状態
図15:キャッシュが古くなっているのでEXPIRED状態

 その裏側では再検証が働いているので、すぐ後にまたアクセスすると、再検証によって得られたキャッシュが使えるのでHIT状態に戻ります(図16)。

図16:再検証が成功してHIT状態
図16:再検証が成功してHIT状態

 意図したキャッシュ更新が行われていそうなことが確認できました。loader側からCache-Controlを設定することで、Webサイトの強力な味方であるCDNキャッシュを設定することができました。

 今回はCloudflareの例でしたが、他のCDNサービスではHTMLやJSONのキャッシュも含めて、また違った戦略を取ることができるかもしれません。各環境に合わせて、工夫してみましょう。

さいごに

 今回は、Remixの得意な領域であるWebサイト制作とは切っても切れない関係にあるCDNキャッシュの制御について解説しました。キャッシュを活用することで、例に挙げた画像生成の重い処理が実行される頻度を下げることができます。速度向上によるユーザーへのメリットだけでなく、サーバー費用の削減にも繋がるため、積極的に活用していきたいですね。

 さて、Remixを題材にしてWeb標準を学んできた本連載も、今回で最終回となります。ルーティングについては独自の考え方もあったものの、通信に関してはWeb標準を最大限活用した設計が行われているフレームワークであることが、本連載を通じてわかっていただけたのではないでしょうか。

 Remixは今後、React Router v7という名前で再出発することがアナウンスされています。本連載で学んだことの大部分は、React Router v7でも役立つはずですし、もし運悪くそうでなかったとしても、皆さんが学んだWeb標準の知識は、どんなに新しいフレームワークが出てきても活かせるものであるはずです。

 RemixとReact Routerの今後の発展にも期待しつつ、本連載を読んでいただいた皆さんが、Web標準を活用したWebサイト制作に挑戦するきっかけになれば幸いです。

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Remixを通じてWebを学ぶ連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 中川幸哉(ナカガワユキヤ)

WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS Twitter: @yyamada(公式)、@yyamada/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/20101 2024/09/20 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング