この記事はHacobu Advent Calendar 2024の22日目の記事です。
vercelの移行先候補としてよくあがるのが cloudflare pages。
そんなcloudflare pagesにnext.jsとauth.jsのスタックで環境を作れないか試してみました。
cloudflare pages & next.jsはすでに詳しく解説されている記事があるので、割愛しつつハマりポイントのみコメントします。ここではauth.js周りの実装例を見ていきたいと思います。
node compatibilityは大事
wranglerの設定ファイルである wrangler.toml
をドキュメントを参考に作成します。
#:schema node_modules/wrangler/config-schema.json
name = "my-next-app"
compatibility_date = "2024-12-16"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".vercel/output/static"
nodeのapiを利用するためworker ランタイムで互換性フラグを有効にします。compatibility_dateは最新のものを選んでください。少し古いものだと、エラーが発生し動かなくなるケースがあります。
wrangler.toml
からbindingsや環境変数の設定を読み取って型情報を生成することができます。
pnpm wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts
生成されたものをtsconfig.json
のincludeに指定することで、CloudflareEnv
という型が利用できます。
wrangler.toml
はcommitするので機密情報は別で管理したいです。localで.env
のようにsecretを読み込めるようにdatabaseのconnectionやgoogleなどの情報を.dev.vars
に登録します。.dev.vars
に登録することでwranglerがsecret情報として扱ってくれ型定義にも反映してくれます。
TURSO_URL=""
TURSO_AUTH_TOKEN=""
AUTH_SECRET=""
AUTH_GOOGLE_ID=""
AUTH_GOOGLE_SECRET=""
@cloudflare/next-on-pagesを追加する
素のnext.jsをbuildしたものをcloudflare pagesにdeployするだけではもちろん動きません。cloudflare pageで動くようにbuildしたものを変換する必要があります。また開発時にもwranglerを起動し環境変数やbindingsをlocalで再現する必要があります。
それを実現してくれるツールが@cloudflare/next-on-pages
です。
pnpm i @cloudflare/next-on-pages
開発時にlocalでbindingを読み取れるようにします。
// next.config.ts
import { setupDevPlatform } from '@cloudflare/next-on-pages/next-dev';
if (process.env.NODE_ENV === 'development') {
await setupDevPlatform();
}
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
};
export default nextConfig;
package.jsonのscriptにbuildコマンドを追加します
"pages:build": "npx @cloudflare/next-on-pages",
これで pnpm pages:buld
でbuildしたものをcloudflare pagesにdeployできるようになりました。
auth.js
基本的にはこちらのドキュメント通りに進めます。
下記コマンドで生成したランダムな文字列はsecretのAUTH_SECRET
に指定してください。
pnpm dlx auth secret
auth.jsはデフォルトでsessionをCookieに保存します。なのでデータベースは必要ないのですが、今回はユーザー情報を保存する想定でデータベースを用意します。データベースはlibsqlのturso、ormはdrizzleを利用します。
こちらもドキュメントに詳しいやり方が書いてるのでそれに従って設定します。
- https://orm.drizzle.team/docs/get-started/turso-new
- https://authjs.dev/getting-started/adapters/drizzle
google認証をできるようにするため google providerを追加します。
ドキュメント通りに進めると最終的にauth.tsはこのようになります。
// auth.ts
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db } from "./schema"
export const { handlers, auth } = NextAuth({
adapter: DrizzleAdapter(db),
providers: [Google],
})
ここまではほぼドキュメント通りに進めるだけです。node runtimeの場合はこれで完了なのですが、cloudflare pagesで動かすためにはもうひと手間加えます。というのも、workerでは process.env
から環境変数が取得できません。auth.jsもdrizzleに getRequestContext
からenvを取得し明示的に渡してあげる必要があります。
// auth.ts
import {getRequestContext} from '@cloudflare/next-on-pages';
import {drizzle} from 'drizzle-orm/libsql/web';
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { schema } from "./schema"
export const { handlers, auth } = NextAuth(() => {
const {env} = getRequestContext();
const db = drizzle({url: env.TURSO_URL, authToken: env.TURSO_TOKEN}, schema);
return {
secret: env.AUTH_SECRET,
adapter: DrizzleAdapter(db),
providers: [
Google({
clientId: env.AUTH_GOOGLE_ID,
clientSecret: env.AUTH_GOOGLE_SECRET,
}),
],
};
})
最後に edge上で動くのでroute.tsにruntimeの宣言を追加します。
// app/api/auth/[...nextauth]/route.ts
import { handlers } from "@/auth"
export const runtime = 'edge';
export const { GET, POST } = handlers
これで動くようになりました🎉
まとめ
簡単そうに見えるのですが、最初に試したときはnode compatibilityに対応していないapiをauth.jsが使っていたりしてエラーになるケースに何度もぶつかりました。またエラーの原因を特定するのも難しく対応されるまで待つ必要がありました。なので、本番環境で安定して動かすには少しハードルが高い気がします。とはいえ、コミュニティは活発なので放置されない安心感はあります。
コストも抑えられCDN上で動くというメリットを考えると、簡単なアプリを作る有力な選択肢にしたいと思いました。