🐕

RemixをCloudflare Workersで動かす & KVでデータをキャッシュする

2021/11/26に公開

前回の記事
https://zenn.dev/catnose99/articles/dfc9c1197daec3

この記事ではRemixをCloudflare Workersにデプロイしつつ、Workers KVで外部APIから取得したデータをキャッシュしてみたいと思います。

Remixをローカルで開発

ドキュメントを参考にしつつ環境構築していきます。

インストール

まず以下のコマンドでRemixをインストールします。

$ npx create-remix@latest

プロンプトで使用するテンプレートを聞かれますが、ここでCloudflare Workersを選択します。

ローカルでの開発

Cloudflare Workersのテンプレートではnpm run startというコマンドが用意されています。このコマンドを実行するとMiniflareというWorkersのローカルシミュレーターが立ち上がります。

このローカルシミュレーターを起動しつつ、ライブリロードが効くようにnpm run devも同時に実行することになります。

$ npm run start # http://127.0.0.1:8787 でプレビューができるように
# ターミナルの別タブを開く
$ npm run dev # これがないとライブリロードが効かない

開発体験としては少し微妙ですが、Cloudlfare Workersは色々と特殊な仕様があるため、Miniflareを使って本番環境に近い形で動かすのは合理的だと思います。

デプロイ

とりあえずソースコードには修正を加えずにそのままデプロイを行ってみます。

wranglerのセットアップ

Cloudflare WorkersではwranglerというCLIを使ってローカルからデプロイできます。wranglerの設定方法は以下の記事で軽く触れています。

https://zenn.dev/catnose99/articles/f86d74cc5abc37

はじめてWorkersを使う場合はダッシュボードで初期設定を済ませておく

はじめてCloudflare Workersを使う場合はCloudflareのダッシュボードから[Workers]タブを選択し、サブドメインを決めておく必要があります。

デプロイ

wranglerが使用できる状態になっていれば以下のコマンドでデプロイを開始できます。

$ npm run deploy

成功するとWorkersのURLが表示されます。開いたときにこんな画面が表示されればOKです。

SSRが動いているか確認

demoアプリではhttps://workersのURL/demos/params/[なにか]にアクセスすると、SSRでなにかが表示されるようになっています。

例えばhttps://workersのURL/demos/params/test-paramsにアクセスすると以下のような表示になります。

レスポンスのhtmlファイルを見てもこの文字列が含まれており、SSRが動いていることが分かります。

KVでAPIレスポンスをキャッシュする

ここからが本題です。以下のようなことをやってみたいと思います。

  • SSRで外部APIからフェッチしたデータを表示
  • データはWorkers KVでキャッシュする

キャッシュなしでSSRした場合

違いが分かりやすいように、まずは普通にSSRするコードを書いてみます。app/routes/index.tsxを以下のようにします。

routes/index.tsx
import dayjs from "dayjs";
import { useLoaderData } from "remix";
import type { LoaderFunction } from "remix";

export let loader: LoaderFunction = async () => {
  const startAt = dayjs(new Date()); // 時間計測開始
  const apiRes = await fetch("https://約3秒後にレスポンスするAPI");
  const endAt = dayjs(new Date()); // 時間計測終了
  return {
    apiRes,
    diff: endAt.diff(startAt)
  };
};

export default function HomePage() {
  const data = useLoaderData<LoaderData>();
  const { apiRes, diff } = data
  return (
    <ul>
      <li>APIレスポンス: <code>{JSON.stringify(apiRes)}</code></li>
      <li>経過時間: {diff} ms</li>
    </ul>
  );
}

超簡単に説明すると、export loaderでSSR時に使用したいデータを取得します(Next.jsのgetServerSidePropsにあたります)。そのデータをuseLoaderData()で取得し、HTMLの中に埋め込むようなイメージです。

外部APIは{ message: "hello" }と返すだけのものですが、キャッシュの効果が分かりやすくするためにあえて3秒間遅延させています。

ページを読み込むと次のような表示になります。

フェッチしたデータと、フェッチリクエストにかかった時間をそのまま表示しているだけです。

KVでキャッシュした場合

続いてAPIからフェッチしたデータをKVにキャッシュしてみます。

KVを使うためには、あらかじめネームスペースを設定しておく必要があります。詳しくは 👇 こちらの折りたたまれた部分を参考にしてください。

KVを使うための事前準備

1. KVのネームスペースを新規追加

$ wrangler kv:namespace create "MY_KV"


2. wrangler.tomlにネームスペースの情報を追記

1を実行したときに表示される以下のようなコードをwrangler.tomlに追記します。

kv_namespaces = [ 
  { binding = "MY_KV", id = "..." }
]


3. ネームスペースの型定義を追加

KVのAPIにはネームスペース.getのような形でアクセスすることになります。そのため、ネームスペース部分を型定義に追加しておきます。

global.d.ts
declare const MY_KV: KVNamespace;

これでMY_KV.getMY_KV.putが使えます。ちなみにKVNamespaceの部分はRemixのCloudflare Workersのテンプレートを使っている場合はあらかじめグローバルに定義されています。

以下のサンプルではCACHESというKVのネームスペースを使用しています。

routes/index.tsx
export let loader: LoaderFunction = async () => {
  const startAt = dayjs(new Date()); // 時間計測開始

  // キャッシュを取得
  const cachedJson = await CACHES.get("cache-key", "json");

  // KVにキャッシュが存在するならそれを返す
  if (cachedJson) {
    const endAt = dayjs(new Date()); // 時間計測終了
    return {
      apiRes: cachedJson.apiRes,
      fromKV: true,
      diff: endAt.diff(startAt),
    };
  }

  // キャッシュがなかったのでフェッチ
  const res = await fetch("https://約3秒後にレスポンスするAPI");
  const apiRes = await res.json();

  // 次回のリクエストのためにKVに保存
  // expirationTtlを指定することで60秒後にキャッシュをクリアする
  await CACHES.put("cache-key", JSON.stringify({ apiRes }), { expirationTtl: 60 });

  const endAt = dayjs(new Date()); // 時間計測終了
  return { apiRes, fromKV: false, diff: endAt.diff(startAt) };
};

export default function HomePage() {
  const data = useLoaderData<LoaderData>();
  const { apiRes, fromKV, diff } = data
  return (
    <ul>
      <li>
        APIレスポンス: <code>{JSON.stringify(apiRes)}</code>
      </li>
      <li>KVからのキャッシュ: {fromKV.toString()}</li>
      <li>経過時間: {diff} ms</li>
    </ul>
  );
}

このページにアクセスすると

  • 初回アクセスは3秒以上かかる
  • 2回目のアクセスはKVのキャッシュが返されるためすぐにレスポンスされる
  • 60秒待ってからリクエストすると再フェッチされるため3秒以上かかる

といった動きになります。スクショはこんな感じ。

スクショはローカル環境のものですが、実際にWorkers上で動かしたときも同じような挙動になります。

Discussion