⚠️注意⚠️: 絶望的な欠点
バンドルサイズが3.6MBと、3MB以上になるため、フリープランでは使えません。
Total Upload: 14251.07 KiB / gzip: 3601.99 KiB
TL;DR
プロジェクトを一から作りたくない場合は、以下のリポジトリをクローンして、READMEに従えば、すぐに開発を始められます。
https://github.com/torimapowered/cloudflare-fullstack
Sources
ベースプロジェクトの作成
cloudflare-fullstack
の部分を任意のプロジェクト名に置き換えてください。
npm create cloudflare@latest -- cloudflare-fullstack --framework=next
選択肢はすべてデフォルトでok
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like your code inside asrc/
directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to use Turbopack fornext dev
? … No / Yes
✔ Would you like to customize the import alias (@/*
by default)? … No / Yes
compilerOptions.target
の変更
tsconfig.json
のcompilerOptions.target
を以下のようにES2022
に変更。
top level awaitを使うために必要。
{
"compilerOptions": {
"target": "ES2022",
}
}
next.config.ts
const nextConfig: NextConfig = {
serverExternalPackages: ["@prisma/client", ".prisma/client"],
};
を追加。
Auth.jsで使う認証情報
.dev.var
に以下のように追記する
AUTH_SECRET = "<replace-me>"
AUTH_RESEND_KEY = "<replace-me>"
AUTH_EMAIL_FROM = "[email protected]"
AUTH_URL = "http://localhost:8787/"
auth.jsのインストール
npm i next-auth@beta
AUTH_SECRET
の作成
npx auth secret
.env.local
にAUTH_SECRET
が作成されるので、
npx wrangler secret put AUTH_SECRET
で値をアップロードする。
そうしたら、もう一度
npx auth secret
を実行してAUTH_SECRET
を再生成する。
? Overwrite existing `AUTH_SECRET`? › (y/N)
と聞かれるのでy
で.env.local
を上書き。
そして生成された新しいAUTH_SECRET
を.dev.vars
に追記する。
.env.local
は消してもよい。
D1のデータベース作成
npx wrangler d1 create cloudflare-fullstack
成功すると
{
"d1_databases": [
{
"binding": "DB",
"database_name": "cloudflare-fullstack",
"database_id": "f4d98234-e618-47c9-9c75-300c2d48deb5"
}
]
}
のような出力がされるので、wrangler.jsonc
に貼り付ける。
既存の$schema
, name
, main
などと同じ層にd1_databases
が来るようにする。
/**
* Bindings
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
* databases, object storage, AI inference, real-time communication and more.
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
*/
というコメント説明があるので、その下に貼り付けるとよいでしょう。
resend.comの設定
npx wrangler secret put AUTH_EMAIL_FROM
で[email protected]
と設定します。
.dev.vars
にも同じく設定します。
npx wrangler secret put AUTH_RESEND_KEY
で、resend.comで取得したAPIキーを設定します。
また、.dev.vars
のAUTH_RESEND_KEY
にも同様にAPIキーを設定しますが、
セキュリティのために、先ほどCloudflareにアップロードしたものとは別のキーを生成して使いましょう。
cloudflare-env.d.ts
の自動生成
npm run cf-typegen
でcloudflare-env.d.ts
を自動的に生成します。
これにより、これまで.dev.vars
やwrangler.jsonc
に設定したものを、typescriptで使えるようになります。
Prisma
npm i -D prisma
npm install @prisma/adapter-d1 @prisma/client
でインストール。
npx prisma init --datasource-provider sqlite
で
app/prisma/schema.prisma
が作成され、.env
にDATABASE_URL
が追記されます。
.gitignore
に.env
を忘れず追加しておきます。
schema.prisma
D1Adapter
などのadapterを使えるようにするために、app/prisma/schema.prisma
のclient
にpreviewFeatures = ["driverAdapters"]
を追記します。
以下のようになります。
generator client {
provider = "prisma-client-js"
output = "../app/generated/prisma"
previewFeatures = ["driverAdapters"]
}
⚠ output
を削除する
schema.prisma
には以下のようにoutput
行が自動的に生成されているはずです。
generator client {
provider = "prisma-client-js"
output = "../app/generated/prisma"
}
通常のprismaではこれがデフォルトであり推奨されていますが、
今回のようにOpenNEXTで使う場合、output
を指定してはいけません(行を削除する必要があります)。
Auth.jsのスキーマを追加
app/prisma/schema.prisma
に、Auth.jsが使用するスキーマ
を以下のように追記します
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
// Optional for WebAuthn support
Authenticator Authenticator[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model VerificationToken {
identifier String
token String
expires DateTime
@@unique([identifier, token])
}
// Optional for WebAuthn support
model Authenticator {
credentialID String @unique
userId String
providerAccountId String
credentialPublicKey String
counter Int
credentialDeviceType String
credentialBackedUp Boolean
transports String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([userId, credentialID])
}
Cloudflareの接続情報をPrismaに渡す
.env
に以下を追記します。
DATABASE_URL
は既存のもの(file:./dev.db
)から置き換えてください。
DATABASE_URL="file:../.wrangler/state/v3/d1/miniflare-D1DatabaseObject/74121e82aac5ba8d638903f7126ebe2f3ce1557d5b8624e7992d5e63599314d0.sqlite"
CLOUDFLARE_ACCOUNT_ID="0773..."
CLOUDFLARE_DATABASE_ID="01f30366-..."
CLOUDFLARE_D1_TOKEN="F8Cg..."
DATABASE_URL
: この箇条書きの下で詳しく説明。CLOUDFLARE_ACCOUNT_ID
: CloudflareアカウントID。npx wrangler whoami
で取得可能。CLOUDFLARE_DATABASE_ID
: データベースのID。D1データベース作成時に取得される。CLOUDFLARE_D1_TOKEN
: このAPIトークンは、Prisma ORMがD1インスタンスと直接通信するために使用されます。作成するには、以下の手順に従ってください:- https://dash.cloudflare.com/profile/api-tokens にアクセス
- 「Create Token」をクリック
- 「Custom token」テンプレートをクリック
- テンプレートを記入:わかりやすい名前を使用し、
Account / D1 / Edit
権限を追加してください。 - 「Continue to summary」をクリックし、次に「Create Token」をクリック。
.wrangler/state/v3/d1/miniflare-D1DatabaseObject/74121e82aac5ba8d638903f7126ebe2f3ce1557d5b8624e7992d5e63599314d0.sqlite
のようなファイルを探してください。
末尾がsqlite-shm
, sqlite-wal
のものがあるかもしれませんが、今は関係ありません。
重要なのはファイル名の乱数で、それが1種類しかない場合はそれを使います。
2種類以上ある場合でも、この乱数はwrangler.jsonc
にコピペしたdatabase_id
のハッシュなので、
自作の計算機
に入れると計算できます。
.wrangler/state/v3/d1/miniflare-D1DatabaseObject
ディレクトリやファイルが見つからない場合はnpx wrangler d1 execute cloudflare-fullstack --command "select 1;"
のような適当なSQLを実行すると生成されます。
これらの環境変数はNextjsのアプリから使われるわけではなく、開発者がprisma migrate dev
などを実行したときに使われるため、
.dev.vars
ではなく.env
に置いています。
remote.prisma.config.ts
remote.prisma.config.ts
を以下の内容で作成します。
import path from 'node:path'
import type { PrismaConfig } from 'prisma'
import { PrismaD1HTTP } from '@prisma/adapter-d1'
// import your .env file
import 'dotenv/config'
type Env = {
CLOUDFLARE_D1_TOKEN: string
CLOUDFLARE_ACCOUNT_ID: string
CLOUDFLARE_DATABASE_ID: string
}
export default {
earlyAccess: true,
schema: path.join('prisma', 'schema.prisma'),
migrate: {
async adapter(env) {
return new PrismaD1HTTP({
CLOUDFLARE_D1_TOKEN: env.CLOUDFLARE_D1_TOKEN,
CLOUDFLARE_ACCOUNT_ID: env.CLOUDFLARE_ACCOUNT_ID,
CLOUDFLARE_DATABASE_ID: env.CLOUDFLARE_DATABASE_ID,
})
},
},
} satisfies PrismaConfig<Env>
Prisma公式から少し改変し、
prisma migration dev
とそれ以外の場合で(簡易的に)挙動を変えています。
prisma.config.ts
に作成すると、prisma migrate dev
であろうとprisma migrate deploy
であろうと自動的に読み込まれてしまいます。
するとprisma migration dev
のときでも本番のD1に影響を与えてしまいます。
そのため、自動的に読み込まれないようファイル名をremote.prisma.config.ts
に変えて、prisma migrate deploy --config ./remote.prisma.config.ts
のように
デプロイ時にだけ読み込むようにします。
package.json
のscripts
に以下を追加しておきましょう。
{
"db:migrate:dev": "prisma migrate dev",
"db:migrate:deploy": "prisma migrate deploy --config ./remote.prisma.config.ts"
}
prisma clientの作成
npx prisma generate
でprisma clientを生成します。
vscodeの場合、後述のPrismaClient
がany
のままである場合があるため、
Ctrl + Shift + P
-> TypeScript: Restart TS Server
を実行しておきましょう。
app/auth.ts
npm install @auth/prisma-adapter
でインストール。
lib/db.ts
を以下のように作成します。
import { getCloudflareContext } from "@opennextjs/cloudflare";
// You can use cache from react to cache the client during the same request
// this is not mandatory and only has an effect for server components
import { cache } from "react";
import { PrismaClient } from "@prisma/client";
import { PrismaD1 } from "@prisma/adapter-d1";
export const getDb = cache(() => {
const { env } = getCloudflareContext();
const adapter = new PrismaD1(env.MY_D1);
return new PrismaClient({ adapter });
});
// If you need access to `getCloudflareContext` in a static route (i.e. ISR/SSG), you should use the async version of `getCloudflareContext` to get the context.
export const getDbAsync = async () => {
const { env } = await getCloudflareContext({ async: true });
const adapter = new PrismaD1(env.MY_D1);
const prisma = new PrismaClient({ adapter });
return prisma;
};
app/auth.ts
を以下のように作成します。
import NextAuth from "next-auth";
import { NextAuthResult } from "next-auth";
import Resend from "next-auth/providers/resend";
import { getCloudflareContext } from "@opennextjs/cloudflare";
import { PrismaAdapter } from "@auth/prisma-adapter";
import { getDbAsync } from "@/lib/db";
const authResult = async (): Promise<NextAuthResult> => {
const prisma = await getDbAsync();
return NextAuth({
providers: [
Resend({
apiKey: (await getCloudflareContext({ async: true })).env
.AUTH_RESEND_KEY,
from: (await getCloudflareContext({ async: true })).env.AUTH_EMAIL_FROM,
}),
],
adapter: PrismaAdapter(prisma)
});
};
export const { handlers, signIn, signOut, auth } = await authResult();
このapp/auth.ts
は、
の公式ドキュメントのコードを組み合わせて(人間が)作成しました。
auth api
app/api/auth/[...nextauth]/route.ts
を以下のように作成する。
import { handlers } from "../../../auth";
export const { GET, POST } = handlers;
middleware.ts
を以下のように作成します。
export { auth as middleware } from "./app/auth";
prismaの初期化
いつもどおりに進めると、prisma migration dev
は成功しますが、その後のprisma migration deploy
で以下のようなエラーが出ます。
Error: P3005
The database schema is not empty. Read more about how to baseline an existing production database: https://pris.ly/d/migrate-baseline
D1には予約済みの_cf_KV
というテーブルがあり、読み書きできません。
なのに確かにテーブルとして存在はしているので、prismaの初期化時のテーブル不存在チェックに引っかかってしまいます。
そのため、ダミーのmigrationファイルを作成し、「テーブルが存在するのはマイグレーションをしたからだ」と思わせることにします。
ダミーのmigration
prisma/migrations/0_init/migration.sql
を作成します。
0_init
はなんでもいいです。
内容は
SELECT 1
とだけ書いておきます。これは何もしないSQLです。
npx prisma migrate resolve --config ./remote.prisma.config.ts --applied 0_init
でダミーのmigrationを適用します。
TableDoesNotExist
エラーが発生しますが、これは初回migrationだから_prisma_migrations
というテーブルが見つからなかったという正常な動作です。
実行後に_prisma_migrations
が作成されます。
最終的にMigration 0_init marked as applied.
のように表示されていれば問題ありません。
最初のマイグレーション
npx prisma migrate dev --name init
でschema.prisma
を開発用ローカルDBに反映させます。
その後、
npm run db:migrate:deploy
で本番のD1に反映させます。
お疲れさまでした!!!