🔥
Cloudflare Pagesで動かすNext.js用のHonoのアダプタを作りたい
TL;DR
多分こんな感じ
import { getRequestContext } from "@cloudflare/next-on-pages";
import type { Hono } from "hono";
export const handle = (app: Hono<any, any, any>) => (req: Request) => {
const requestContext = getRequestContext();
return app.fetch(req, requestContext.env, requestContext.ctx);
};
はじめに
Next.js と Hono、Cloudflare Pages の組み合わせは、個人的にとても気に入っているが、一方で気になっている点もある。それは、Context
からBindings
などにアクセス出来ないことだ。せっかくならそういった差異を気にせずに開発を進めたい。
という訳で、それ用のアダプタを作りたい。
準備
とりあえず、以下のコードが動くことを目標とする。
wrangler.toml
compatibility_date = "2024-03-04"
compatibility_flags = ["nodejs_compat"]
[[kv_namespaces]]
binding = "MY_KV"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
env.d.ts
type CloudflareEnv = {
MY_KV: KVNamespace;
}
route.ts
import { Hono } from "hono";
export const runtime = "edge";
const app = new Hono<{ Bindings: CloudflareEnv }>()
.basePath("/api")
.get("/hello", async (c) => {
let responseText = "Hello World";
const myKv = c.env.MY_KV;
const suffix = await myKv.get("suffix");
responseText += suffix ?? "";
c.executionCtx.waitUntil(myKv.put("suffix", " from a KV store!"));
return c.text(responseText);
});
本題
普段は以下のように vercel アダプタを用いている。
route.ts
import { Hono } from "hono";
+import { handle } from "hono/vercel";
export const runtime = "edge";
const app = new Hono<{ Bindings: CloudflareEnv }>()
.basePath("/api")
.get("/hello", async (c) => {
let responseText = "Hello World";
const myKv = c.env.MY_KV;
const suffix = await myKv.get("suffix");
responseText += suffix ?? "";
c.executionCtx.waitUntil(myKv.put("suffix", " from a KV store!"));
return c.text(responseText);
});
+export const GET = handle(app);
しかし、前述の通り、Context
からBindings
にアクセス出来ないため、以下のようなエラーが発生する。
[TypeError: Cannot read properties of undefined (reading 'get')]
参考までに、v4.0.10 時点でのhono/vercel
の実装を覗いてみる。
思っていたよりも簡素……。
ちなみに、app.fetch
は以下のようになっている。
どうやら、Request
の他にEnv["Bindings"]
とExecutionContext
を引数に取るようだ。
幸いにも、これらは@cloudflare/next-on-pages
のgetRequestContext
から取得できる。
これを元に、アダプタを作ってみる。
adapter.ts
import { getRequestContext } from "@cloudflare/next-on-pages";
import type { Hono } from "hono";
export const handle = (app: Hono<any, any, any>) => (req: Request) => {
const requestContext = getRequestContext();
return app.fetch(req, requestContext.env, requestContext.ctx);
};
これをhono/vercel
から差し替えれば、Bindings
やExecutionContext
にアクセスできるようになるはずだ。
route.ts
import { Hono } from "hono";
-import { handle } from "hono/vercel";
+import { handle } from "./adapter";
export const runtime = "edge";
const app = new Hono<{ Bindings: CloudflareEnv }>()
.basePath("/api")
.get("/hello", async (c) => {
let responseText = "Hello World";
const myKv = c.env.MY_KV;
const suffix = await myKv.get("suffix");
responseText += suffix ?? "";
c.executionCtx.waitUntil(myKv.put("suffix", " from a KV store!"));
return c.text(responseText);
});
export const GET = handle(app);
おわりに
まだ、細かい差異はあるものの、概ね期待通りに動いた。これで開発がより快適になるだろう。
Discussion