はじめに
Next.jsでCloudflareを利用する際にD1をローカルと本番で切り替えるのに手間取ったのでまとめていきます。
環境構築手順
まずはNext.jsの環境構築を行います。
私が試したところ最新のNext.jsでは動かないところがあったので14で行います。
npx create-next-app@14
✔ What is your project named? … my-app
✔ 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 to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias (@/*)? … No / Yes
次にCloudflareのドキュメントどおりに設定をしていきます。
cd my-app
npm i -D @cloudflare/next-on-pages
touch wrangler.toml
name = "my-app"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".vercel/output/static"
import { setupDevPlatform } from '@cloudflare/next-on-pages/next-dev';
/** @type {import('next').NextConfig} */
const nextConfig = {};
if (process.env.NODE_ENV === 'development') {
await setupDevPlatform();
}
export default nextConfig;
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"pages:build": "npx @cloudflare/next-on-pages",
"preview": "npm run pages:build && wrangler pages dev", // 追加
"deploy": "npm run pages:build && wrangler pages deploy" // 追加
},
"dependencies": {
"next": "14.2.31",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@cloudflare/next-on-pages": "^1.13.13",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.31",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
デプロイを試してみます
npm run deploy
✔ The project you specified does not exist: "my-app". Would you like to create it? › Create a new project
✔ Enter the production branch name: … main
表示されたURLにアクセスして以下の画面がでればOKです
次にD1のデータベースを作成していきます。
$ npx wrangler@latest d1 create tutorial
[[d1_databases]]
binding = "DB"
database_name = "tutorial"
database_id = "587b89e5-2a1d-4505-9423-43153d815b2d"
データベースが作成できたら接続情報が表示されるのでwrngler.toml
に追加します。
name = "my-app"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]
pages_build_output_dir = ".vercel/output/static"
[[d1_databases]]
binding = "DB"
database_name = "tutorial"
database_id = "587b89e5-2a1d-4505-9423-43153d815b2d"
次にDrizzleの設定をします。
npm install drizzle-orm
npm install -D drizzle-kit
npm install better-sqlite3
touch drizzle.config.ts
mkdir db
touch db/index.ts
touch db/schema.ts
import { defineConfig } from "drizzle-kit";
import fs from "node:fs";
import path from "node:path";
function getLocalD1DB() {
try {
const basePath = path.resolve(".wrangler/state/v3/d1/miniflare-D1DatabaseObject");
const dbFile = fs
.readdirSync(basePath, { encoding: "utf-8", recursive: true })
.find((f) => f.endsWith(".sqlite"));
if (!dbFile) {
throw new Error(`.sqlite file not found in ${basePath}`);
}
const url = path.resolve(basePath, dbFile);
return url;
} catch (err) {
console.log(`Error ${err}`);
}
}
export default defineConfig({
dialect: "sqlite",
schema: "./db/schema.ts",
out: "./drizzle",
...(process.env.NODE_ENV === "production"
? {
driver: "d1-http",
dbCredentials: {
accountId: process.env.CLOUDFLARE_D1_ACCOUNT_ID,
databaseId: process.env.DATABASE,
token: process.env.CLOUDFLARE_D1_API_TOKEN,
},
}
: {
dbCredentials: {
url: getLocalD1DB(),
},
}),
});
import { drizzle } from "drizzle-orm/d1";
import * as schema from "./schema";
// D1Databaseの型定義
declare global {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface D1Database {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
prepare: (query: string) => any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
exec: (query: string) => any;
}
}
// Cloudflare Pages環境用のD1データベース接続関数
export function createDB(d1: D1Database) {
return drizzle(d1, { schema, logger: true });
}
// 開発環境用(必要に応じて)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const db = drizzle(process.env.DB as any, { schema, logger: true });
import { sqliteTable, text } from "drizzle-orm/sqlite-core";
export const users = sqliteTable("user", {
id: text("id")
.primaryKey()
.$defaultFn(() => crypto.randomUUID()),
name: text("name"),
});
Drizzleのマイグレーションを行うためのコマンドを追加します
{
"name": "my-app",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"pages:build": "npx @cloudflare/next-on-pages",
"preview": "npm run pages:build && wrangler pages dev",
"deploy": "npm run pages:build && wrangler pages deploy",
"db:generate": "drizzle-kit generate",
"db:migrate:dev": "drizzle-kit migrate", // 追加
"db:migrate:prod": "NODE_ENV=production drizzle-kit migrate" // 追加
},
"dependencies": {
"drizzle-orm": "^0.44.4",
"next": "14.2.31",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@cloudflare/next-on-pages": "^1.13.13",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"drizzle-kit": "^0.31.4",
"eslint": "^8",
"eslint-config-next": "14.2.31",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}
DrizzleはDBに接続するので接続情報を.envに追加しましょう
touch .env
CLOUDFLARE_ACCOUNT_ID=あなたのアカウントID
CLOUDFLARE_DATABASE_ID=あなたのデータベースID
CLOUDFLARE_D1_TOKEN=あなたのD1トークン
preview_database_id = "DB"
それぞれの値は以下の記事を参考に埋めてください
それではマイグレーションを行ってみましょう
まずはローカルから行ってみます
npm run db:generate
npm run db:migrate:dev
次はテストデータをローカルのD1に追加します
npx wrangler d1 execute tutorial --local --command "INSERT INTO user (id, name) VALUES ('test-user-1', '田中太郎'), ('test-user-2', '佐藤花子'),
npx wrangler d1 execute tutorial --local --command "SELECT * FROM user"
ではAPIでDrizzleを使ってローカルのD1から実際にデータを取得できるか確かめます
mkdir -p app/api/users
touch app/api/users/route.ts
import { createDB } from "@/db";
import { users } from "@/db/schema";
import { NextResponse } from "next/server";
export const runtime = "edge";
export async function GET() {
try {
// Cloudflare Pages環境からD1データベースを取得
const env = process.env.DB as unknown as D1Database;
if (!env) {
return NextResponse.json({ error: "Database not available" }, { status: 500 });
}
const db = createDB(env);
const res = await db.select().from(users);
return NextResponse.json(res);
} catch (error) {
console.error("Error fetching users:", error);
return NextResponse.json({ error: "Failed to fetch users" }, { status: 500 });
}
}
curl localhost:8788/api/users
[{"id":"test-user-1","name":"田中太郎"},{"id":"test-user-2","name":"佐藤花子"},{"id":"test-user-3","name":"鈴木一郎"}]
ローカルのD1に接続確認ができたので、次は本番でもできるかを確認します
本番にデータを追加しておきます。
npm run db:migrate:prod
npx wrangler d1 execute tutorial --remote --command "INSERT INTO user (id, name) VALUES ('test-user-1', 'watanabe')"
npx wrangler d1 execute tutorial --remote --command "SELECT * FROM user"
データが追加できたのでデプロイしてAPIを叩いてみます
npm run deploy
curl https://1b8e3b32.my-app-5h7.pages.dev/api/users
[{"id":"test-user-1","name":"watanabe"}]
URLはデプロイ先のURLに各自変えてください。
ちゃんと返ってきたのでうまく切り替えができていそうです。
おわりに
サンプルのリポジトリをおいておきます