Cloudflare D1+ Next.jsを試してるがうまく動かない
事前準備
code:sh
$ node -v
v16.13.0
$ npm install -g wrangler
Next.jsをCloudflare Pagesで動かす
Cloudflare PagesはNext.js 12までしか対応してなかった記憶だったけどいつの間にかNext.js 13対応が終わっていた。
https://blog.cloudflare.com/pages-function-goes-ga/
どうやってNext.jsがCloudflare Pagesで動くようになるのかはCloudflare PagesにNext.jsをデプロイするとSSRが動作するようになったのでどうやって実現されたのかを調べたが詳しい。
中身はなんでもいいのでとりあえず公式のDeploy a Next.js site · Cloudflare Pages docsを見ながらNext.jsのサイトのデプロイを試してみる。
まずデフォルトのpages/api/hello.tsのままではEdge Runtimeでは動かないので全部変更する。
code:ts
import type { NextRequest } from "next/server";
export const config = {
runtime: "experimental-edge",
};
export default async function (req: NextRequest) {
return new Response(JSON.stringify({ name: "John Doe" }), {
status: 200,
headers: {
"Content-Type": "application/json",
},
});
}
次にnext.config.jsも変更する。
code:ts
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
runtime: "experimental-edge",
},
reactStrictMode: true,
swcMinify: true,
};
module.exports = nextConfig;
あとはGitHubで新しいリポジトリを作成してPush。
次にCloudflareのダッシュボードのPages > Create a project > Connect GitHubに移動し、FrameworkとしてNext.jsを選択(static exportではない方)。Environment Variables(advanced)へNODE_VERSIONにv14以上を設定。これでデプロイを行う。
デプロイが完了したらSettingsへ移動。Next.jsのStreams APIを有効にするためにFunctionsのCompatibility flagsにてstreams_enable_constructorsとtransformstream_enable_standard_constructorを設定する。ちなみにこのフラグは2022-11-30には不要になるとのこと。
これでhttps://YOUR-PROJECT-NAME.pages.dev/api/helloを叩くとレスポンスが帰ってくるはず。
D1を導入する
Next.jsからWorkersのD1にどうやってアクセスしたら良いかが鍵っぽい。
そもそもNext.jsのサーバーサイド処理がPages+Workersで動くざっくりとした仕組みについて。
next-on-pagesでnext.jsアプリをビルドした結果、.vercel/outputに生成される
これがPagesにデプロイされる
実はnext-on-pagesはビルド時に.vercel/output/static/_worker.jsというのを生成してる
pages/apiやSSRの処理などを解析してnext-on-pages/templates/_worker.jsを元に_worker.jsを生成する仕組み
Pagesでは_worker.jsがある場合はその内容を元に飛んできたリクエストをWorkersへ丸っと投げることができる
Advanced Mode · Cloudflare Pages docs
とりあえず使えるかどうかわからないが、npm install @cloudflare/d1をしてNext.jsからenv.DBでWorkersへアクセスできるか試す。
先のpages/api/hello.tsを以下のように変更する。envを受け取ってみるようにしただけ。
code:ts
import type { NextRequest } from "next/server";
import type { Database } from "@cloudflare/d1";
export const config = {
runtime: "experimental-edge",
};
export interface Env {
DB: Database;
}
export default async function (req: NextRequest, env: Env) {
return new Response(
JSON.stringify({ name: "John Doe", env: JSON.stringify(env) }),
{
status: 200,
headers: {
"Content-Type": "application/json",
},
}
);
}
これで一旦GitHubへPushしてPagesへ反映する。
次にD1のデータベースを用意する。これはGet started · Cloudflare D1 docsに沿ってWorkersに適当なD1のデータベースを作成するだけ。DB_NAMEはSAMPLE_DBでBINDING_NAMEはDB`にしておく。
Pagesの設定でD1にアクセスできるようService Bindingを設定する。ダッシュボードのPages > your project > Settings > Functions > D1 database bindings にVariable nameをDBにしてD1 databaseをSAMPLE_DBにする。
https://YOUR-PROJECT-NAME.pages.dev/api/helloへアクセスしてみる。
code:json
{
name: "John Doe",
env: "{"u":[],"h":false,"sourcePage":"/api/hello"}"
}
ダメっぽい。
next-on-pagesのtemplate/_worker.jsのtype EdgeFunctionにenvを追加し、fetchのenvを差し込むように手を加えればNext.js側のfunctionの第二引数にenvが渡ってきそうな気がする。
ということは下記の流れでいけるか?
envを差し込むように手を入れたカスタムnext-on-pagesを作成
Next.jsのタスクにカスタムしたnext-on-pagesを使うpackage.jsonに定義
CloudflareのPagesのビルドでそのタスクコマンドを使うようにする
next-on-pagesのカスタムについて。
まずローカルにフォークしたnext-on-pagesをクローンしてきて適当にnpm buildする。生成されたCLIを使い、適当なNext.jsプロジェクトのディレクトリでnode ../../../next-on-pages/dist/index.js --experimental-minifyを実行する。すると.vercel/outputにEdgeへビルドされるための結果が生成される。次にtemplates/_worker.jsのfetch(request, env, context)で__FUNCTIONS__に手を入れてるところでdefaultの引数にenvも渡すようにする。これでNext.jsのapiにenvも渡せるか?
Next.jsのタスクにカスタムしたnext-on-pagesを使うpackage.jsonに定義について。
Next.jsのpackage.jsonにてnpm run custom-bulidでnext-on-pagesでビルドが走るようにした。
CloudflareのPagesのビルドでそのタスクコマンドを使うようにするについて。
ダッシュボードからPagesのビルド設定でnpm run custom-buildを使うように設定。
これで試すとPagesへのデプロイは問題なくできた。がAPIにアクセスしてみるとError 1101 Worker threw exceptionというWorkerのエラーが発生...。Cloudflare 1XXX エラーのトラブルシューティング – Cloudflareヘルプセンターを確認すると1101エラーはCloudflare Workerが、JavaScriptランタイム例外をスローします。とのこと。
envを引数として差し込んだけどランタイム側ではシグネチャエラーになってるのかも。引数を増やす方向で拡張するのではなく、req引数にenvを追加する形で解決できないか試す。
templates/_worker.jsにCfRequestというRequest & {env: ...}な型を定義てreq引数にenvを加えて再度挑戦。
だめだ...。エラーは発生しないけどreq引数にenvが渡されない。もしかしてreq中身がどこかでfilteringされている?
templates/_worker.jsでentrypoint.defaultに渡しているcontextをenvに変えて試してみたがこれも1101エラーが発生してダメ。_worker.jsのfetchのシグネチャはfetch(req, context)でないとダメっぽい。
request/contextの拡張もだめだしシグネチャを変更するのもだめということでもしかするとexperimental-edgeなランタイムでは何か制限されているのかもしれない。
追記: 2023/1/26
下記isssueに公式のコメントがついた。
・@cloudflare/next-on-pages@0.3.0 will be released soon — follow along here: Version Packages #60.
・process.env will contain the environment object you're used to seeing in Cloudflare Workers, populated with the bindings of your project.
・D1 bindings won't work immediately, but you can expect them to follow soon.
・Node.js compatibility, including async_hooks's AsyncLocalStorage can tested in workerd today, and it will be coming to Cloudflare Workers.
https://github.com/cloudflare/next-on-pages/issues/1#issuecomment-1403845325
要は、
v0.3.0からprocess.env経由にはなるが各種bindingsにアクセスできるようになる
D1だけはwranglerの方もいじらないといけないから少し遅れる。
AsyncLocalStorageがWorkersにくるよ
ってことらしい。
D1も使えるようになったらNext.js on Workersが結構実用的になると思うから楽しみ
追記: 2023/3/10
D1 Bindings · Issue #61 · cloudflare/next-on-pages
このPRがCloseになり遂にNext.js on WorkersでD1へアクセスできるようになった🎉
お試しAPI
エンドポイント: https://next-d1-sample.pages.dev/api/hello
ソースコード: https://github.com/YuheiNakasaka/next-d1-sample/blob/main/pages/api/hello.ts
リソース
Deploy a Next.js site · Cloudflare Pages docs
Get started · Cloudflare D1 docs
next-on-pages
Making static sites dynamic with Cloudflare D1
はじめてのCloudflare D1アプリ
cloudflare/templates: A collection of stater templates and examples for Cloudflare Workers and Pages
Environment variables and bindings on env · Issue 1 · cloudflare/next-on-pages
cloudflare/workerd: The JavaScript / Wasm runtime that powers Cloudflare Workers
cloudflare/wrangler2: ⛅️ The CLI for Cloudflare Workers®
Advanced Mode · Cloudflare Pages docs
https://twitter.com/razokulover/status/1595080662826250240
cloudflare