🚀

【新機能】Vercel KVで遅いAPIレスポンスをキャッシュする

2023/05/02に公開

こんにちは
昨日(5/1深夜)のVercel Shipで、Vercel KV, Vercel Postgress, Vercel Blob とストレージに関する三つの新機能が発表されました。

昨今のフロントエンド情勢ではCDNを使ったキャッシュ戦略など、低コストに大規模なトラフィックを捌く需要が高まっており、今までVercelでサポートが薄かったストレージ周りが、気軽に使えるようになるのはかなり嬉しいです。

今回は、発表された三つの新機能の中から、Vercel KVについて、簡単にキャッシュ用途で試して見たので記事に書いてみます。

KVって何?

key Valueの略称で、名前の通りキーと値のペアを保存することができます。

Vercel KVは、中身はRedisデータベースのようなものになっていて、キーとJSONのペアなどをエッジ上で扱うことができます。

https://vercel.com/docs/storage/vercel-kv

Vercel KVを試す

Vercel KVを使ったなるべくシンプルな例として、遅い外部APIのレスポンスをキャッシュする用途で使って、サンプルアプリを作成します。
プロジェクトはNext.jsappDirを使用して作成します。

サンプルアプリのゴール

アクセスしたユーザー名を含むパスに対して挨拶を返す遅いAPIがあります。このAPIレスポンスに基づいて表示される画面を、KVを用いて2回目以降早く表示されるようにします。

KVの用途として、セッションの管理など様々あるのですが、KVを一番シンプルに使えそうな例を考えてキャッシュとしました。

リポジトリ

https://github.com/2ndPINEW/vercel-kv-playground

以下のリンクにデプロイしてあるので、試してみることもできます。
URLのpiを好きな文字列に書き換えると挨拶をしてくれます。
https://vercel-kv-playground.vercel.app/greeting/pi

プロジェクトを作る

npx create-next-appでプロジェクトを作ります

✔ What is your project named? … vercel-kv-playground
✔ Would you like to use TypeScript with this project? … Yes
✔ Would you like to use ESLint with this project? … Yes
✔ Would you like to use `src/` directory with this project? … No
✔ Would you like to use experimental `app/` directory with this project? … Yes

一旦VercelにデプロイしてKVを接続する

一旦この状態でデプロイして、Vercelのプロジェクトを作成します。
プロジェクトを作成したらStorageタブのCreateからKVを作成します。

ダイアログを開くので、Create Newを押して続けます。

次にDBの名前と、リージョンを選択します。KVが使えるリージョンは一部で日本リージョンはありません。KVでは1箇所の書き込み用リージョンと、4箇所の読み込み用リージョンを用意できるので、利用元から近いリージョンを適時選択してください。

最後にプロジェクトと接続すると、そのプロジェクトでKVを使うことができます。

プロジェクトに接続までできると、ダッシュボードで設定するべき環境変数をみることができます。

これを.env.localに貼り付けるとローカルでもVercel KVを使うことができます。

レスポンスが遅いAPIを用意する

開発の準備が整ったので、クエリパラメータで受け取った名前に対して、挨拶を返すだけの簡単なAPIを用意します。
このAPIがリクエストに時間がかかるものとして、3秒間待ってからレスポンスを返すようにしました。

@/app/api/greeting/route.ts
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const name = searchParams.get("name");

  const data = { message: `Hello ${name}` };
  await new Promise((resolve) => setTimeout(resolve, 3000));
  return NextResponse.json(data);
}

名前を含んだURLから挨拶を表示する

/greeting/{name}にアクセスした時に、挨拶を表示するページを用意します。

このページはURLに含まれる名前をSayHelloコンポーネントに渡すだけです。

@/app/greeting/[name]/page.tsx
import SayHello from "@/components/say-hello";
import { Suspense } from "react";

export const dynamic = "force-dynamic";

export default function Page({ params }: { params: { name: string } }) {
  return (
    <main>
      <Suspense>
        {/* @ts-expect-error Async Server Component */}
        <SayHello name={params.name} />
      </Suspense>
    </main>
  );
}

SayHelloコンポーネントではAPIを叩いてユーザーに表示するメッセージを取得、表示します。

@/components/say-hello
export default async function SayHello({ name }: { name: string }) {
  const res = await fetch(
    `https://vercel-kv-playground.vercel.app/api/greeting?name=${name}`
  );
  const { message } = await res.json();
  return <p>{message}</p>;
}

これで、/greeting/eitoのようにアクセスすると、3秒ほどでHello eito!と挨拶が返ってくると思います。
このままだと表示が遅いので、KVを使って改善します。

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

先ほどの例で挨拶が表示されるようになったのですが、APIレスポンスを待たなきゃいけなくて表示が遅いです。
KVにAPIレスポンスを保存して、再利用できるようにしてみます。

まずvercel/kvからkvをインポートします。

import kv from "@vercel/kv";

KVはキーと値のペアになるので、名前から保存する時のキーを生成する関数を作ります。

function kvKey(name: string) {
  return `${name}-key`;
}

APIレスポンスを書き込んだり読み込んだりする時のキーはこの関数を使います。

次に、APIレスポンスをKVに書き込みます。

await kv.json.set(kvKey(name), "$", { message });

kv.json.getなどのメソッドの使い方は、Redisコマンドのドキュメントが参考になります。
vercel/kvのドキュメントに例が書いていないようなコマンドは、こちらを見るとわかりやすいと思います。

KVから読み取る。

const kvValue = await kv.json.get(kvKey(name), "$");

値があればkvValueが配列で返ってくるので、これを使うようにすればOKです。

コードの全体は以下のようになりました。

import kv from "@vercel/kv";

export default async function SayHello({ name }: { name: string }) {
  const kvValue = await kv.json.get(kvKey(name), "$");
  if (kvValue) {
    return <p>{kvValue[0].message} from kv</p>;
  }

  const res = await fetch(
    `https://vercel-kv-playground.vercel.app/api/greeting?name=${name}`
  );
  const { message } = await res.json();

  await kv.json.set(kvKey(name), "$", { message });
  return <p>{message} from api</p>;
}

function kvKey(name: string) {
  return `${name}-key`;
}

これで2回目以降のアクセスでは早く表示されるようになったので、目標は達成です🙌

最後に

Vercel KVRedis互換でサポートしてるAPIが多いおかげでシンプルにJSONを扱えたりして便利でした。
ストレージ周りの大幅強化からの、まだまだ発表が続くみたいなので今後も楽しみです!

Discussion