RemixをCloudflare Workersで動かす & KVでデータをキャッシュする
前回の記事
この記事では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の設定方法は以下の記事で軽く触れています。
はじめて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
を以下のようにします。
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
のような形でアクセスすることになります。そのため、ネームスペース部分を型定義に追加しておきます。
declare const MY_KV: KVNamespace;
これでMY_KV.get
やMY_KV.put
が使えます。ちなみにKVNamespace
の部分はRemixのCloudflare Workersのテンプレートを使っている場合はあらかじめグローバルに定義されています。
以下のサンプルではCACHES
というKVのネームスペースを使用しています。
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