1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AstroのアプリケーションをCloudflare Workersにデプロイする(KVキャッシュ実装)

Posted at

本記事の概要

Astro+Cloudflare Workers+KVを組み合わせて、高速かつ効率的なキャッシュ機能を持つフロントエンドを実装する方法を解説します。
ローカル環境でもキャッシュ動作を検証できる設定を紹介します。

目次

Astroとは?

高速なWebサイトを構築することを目的として作られたWebフレームワークです。複雑なWebアプリケーションを開発するためのフレームワークというより、パフォーマンスにすぐれたウェブサイト(ブログ、ポートフォリオ等)を構築するのに適しています。

特徴:

  • React,Svelte,Vue,HTMX等のウェブコンポーネントをサポート
  • サーバーファーストでレンダリング処理をクライアントサイドでは行わない。デフォルトではゼロ
  • 必要に応じてクライアントサイドのレンダリングも実装可能

Cloudflare Workersとは

Cloudflareのエッジネットワーク上で動くサーバーレスの実行環境です。
全世界330か所以上のCDNノードでコードが実行され、ユーザーの近くで処理されるため、高速なレスポンスが可能になります。

Cloudflare Workersは、ReactAstroHonoVueNuxtSvelteなどの様々なフレームワークと組み合わせて利用することが可能であり、2025年第2四半期にはNext.jsAngularが利用できるようになるそうです。
また、KV(Key-Valueストア)のキャッシュ機能や、R2(オブジェクトストレージ)を簡単に利用することができます。

参考文献

実装: Astro+Cloudflare+KVでキャッシュ実装する

integrationsは、利用するフレームワークを書いてください。

// astro.config.mjs
import alpinejs from "@astrojs/alpinejs";
import cloudflare from "@astrojs/cloudflare";
import react from "@astrojs/react";
import tailwind from "@astrojs/tailwind";
import { defineConfig } from "astro/config";
import Icons from "unplugin-icons/vite";

// https://astro.build/config
export default defineConfig({
  site: "https://example.com/",
  integrations: [
    tailwind(),
    react(),
    alpinejs(),
  ],
  output: "server",
  adapter: cloudflare(),
  vite: {
    plugins: [
      Icons({
        compiler: "astro",
      }),
    ],
  },
});

wrangler.toml

  • preview_id はローカルプレビュー用のKVネームスペースIDです。分かりやすい文字列を設定してください
  • id は本番環境用のKVネームスペースIDです。適切なIDを設定してください
  • Astroの内部で使われているReadableStreamを利用するためにはcompatibility_date2022-11-30以降にしてください
# wrangler.toml
name = "kikudesuyo"
pages_build_output_dir = "./dist"
compatibility_date = "2022-11-30"

[[kv_namespaces]]
preview_id = "preview_id"
binding = "KV_CACHE_BINDING"
id = "your_kv_id"

compatibility_dateの参考文献

キャッシュ実装

  • Cloudflare WorkersのKVストアを利用してAPIレスポンスをキャッシュ
  • キャッシュキーをメソッド名+引数から生成
  • キャッシュがあれば返却、なければAPIを呼んで結果をキャッシュに保存して返す
const CACHE_PREFIX = "blog_cache";
const CACHE_TTL = 3600; // 1時間


//キャッシュキーの生成
function generateCacheKey(method: string, params: (string | number | undefined | null)[]) {
  return [
    CACHE_PREFIX,
    method,
    ...params.filter((p) => p !== undefined && p !== null),
  ].join(":");
}

// Cloudflare WorkersのKVストアを使ったキャッシュ管理クラス
export class CacheManager {
  private kv: KVNamespace;

  constructor(kv: KVNamespace) {
    this.kv = kv;
  }

  async cachedFetch<T>(
    methodName: string,
    params: (string | number | undefined | null)[],
    fetcher: () => Promise<T>,
    ttlSeconds = CACHE_TTL,
  ): Promise<T> {
    const key = generateCacheKey(methodName, params);
    const cached = await this.kv.get<T>(key, "json");
    if (cached) {
      return cached;
    }
    const result = await fetcher();
    await this.kv.put(key, JSON.stringify(result), { expirationTtl: ttlSeconds });
    return result;
  }
}

Astroアプリでの呼び出し例

  • wrangler.tomlで設定したKVバインディング名(例: KV_CACHE_BINDING)に合わせて記述してください
// src/pages/index.astro


// fetchBlogListFromAPI関数は設定したいAPIに差し替えてください。
// パスも同様に適切に設定してください。
---
import {CacheManager} from "your_path";
import {fetchBlogListFromAPI} from "your_path";

const binding = (Astro.locals as any).runtime.env.KV_CACHE_BINDING

const cacheManager = new CacheManager(binding);

const blogList = await cacheManager.cachedFetch(
  "getBlogList",
  [locale, page],
  () => fetchBlogListFromAPI("ja", 1),
);
---


<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>ブログ一覧</title>
  </head>
  <body class="bg-gray-50 text-gray-900">
    <main class="max-w-3xl mx-auto p-6">
      <h1 class="text-3xl font-bold mb-8">最新ブログ記事</h1>
      <ul class="space-y-8">
        {blogs.map((blog) => (
          <li key={blog.id} class="border-b border-gray-300 pb-6 last:border-none">
            <h2 class="text-xl font-semibold text-blue-600 hover:underline cursor-pointer">
              {blog.title}
            </h2>
            <p class="mt-2 text-gray-700">{blog.excerpt}</p>
            <time class="block mt-2 text-sm text-gray-500">{blog.date}</time>
          </li>
        ))}
      </ul>
    </main>
  </body>
</html>

ローカル環境での確認

  • アプリ起動
    astro dev
  • APIを叩いてみる👀
    • ルートディレクトリに.wrangler/state/v3/kv/~が作成されてキャッシュデータが保存されます

スクリーンショット 2025-08-11 1.45.22.png

2回目以降はAPIを叩くことなくキャッシュデータを参照されます🔥

[備忘録] Cloudflare Workers開発の背景

  • Cloudflare Workers SDK V3以降で--proxyオプションが廃止され、Viteを使用したローカル環境の開発体験が著しく低下しました。

[参考文献]

  • 発表に伴いViteでの開発者の反発が多く、これをうけて、CloudflareはVite用の公式プラグインをリリースした
  • このプラグインはproxyを設定不要で、ローカル環境でも本番に近い開発体験を実現しています

なお、Astro.config.mjsにて、Viteプラグインを登録した場合については、開発をすればこちらは内部でProxyを実装しているため特に意識する必要はないです。Astro以外のフレームワークを使って
Vite+Cloudflare Workersにデプロイする場合には、こちらが参考になります。

// vite.config.ts
import { defineConfig } from "vite";
import { cloudflare } from "@cloudflare/vite-plugin";

export default defineConfig({
  plugins: [cloudflare()]
});

おわりに

まだまだAstro+Cloudflare Workersを利用した技術ブログが少なく、実装するためには公式ドキュメントやgithubのissueを深くまで見る必要がありました。
また、Cloudflareの公式ドキュメントは英語のみなので理解するのに時間がかかるため、この記事が参考になると幸いです。
今回はAstroを採用してアプリを実装しましたが、他のSPAやSSRフレームワークでもCloudflareの理解が進むと思います。
詰まった方や内容に疑問点がありましたらぜひコメントをしてください。分かる範囲であれば回答いたします👀

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?