Next.js + Cloudflare Deployment: Production D1 and Local SQLite Database Solution
When developing the W3Cay project, I needed to implement: production environment uses Cloudflare D1 database, local development uses SQLite. This involves challenges such as runtime compatibility, build configuration, database connection management, etc.
Technology Stack
Project Background
W3Cay is a navigation site project with the following technology stack:
- Frontend Framework: Next.js 15
- Deployment Platform: Cloudflare Pages
- Database: Cloudflare D1 (production)
- Local Development: SQLite
Why Not Use Supabase?
Why didn't I use Supabase? Mainly because I feel D1 is more conveniently integrated with the CF platform, and the quota is generous enough to basically never run out. I was worried that if website traffic increases, Supabase's free tier wouldn't keep up. Here are some comparisons:
D1 vs Supabase
| D1 | Supabase | |
|---|---|---|
| Latency | Same network as Pages, zero latency | Needs to route to central database, has network hops |
| Free Tier | 25,000 reads/day, 5GB storage | 500MB storage, 500MB transfer |
| Overage Cost | $0.001/thousand reads | $25/month starting |
| Deployment | Integrated with Cloudflare | Independent deployment |
Why Not Use D1 for Local Development?
The development experience of wrangler pages dev is poor:
- No Hot Reload: After modifications, you need to rebuild (
npm run cf:build && npm run cf:preview) - Difficult Debugging: Edge Runtime breakpoints are not as comprehensive as Node.js
- Tedious Process: Every modification requires build → preview → access
If you use local next dev + sqlite, it's very convenient, basically real-time hot reload to see data
Now I'll summarize how I did the adaptation
Core Challenges
- Edge Runtime: Runs on V8 Isolate, doesn't support Node.js API
- Node.js Runtime: Supports full Node.js ecosystem
better-sqlite3 needs Node.js native modules and can only run in Node.js Runtime
Also, service rendering routes and APIs in Next projects need to declare edge runtime to compile successfully
Otherwise, it will throw an error:
⚡️ Please make sure that all your non-static routes export the following edge runtime route segment config:
⚡️ export const runtime = 'edge';
This is the contradiction point. Local sqlite is based on node's local read/write API implementation. If you use edge environment, you can't use it, but Cloudflare's compilation requires declaring edge
Solution
Managing Edge Runtime Declarations
First, solve the local environment declaration issue. By default, local uses node environment, compile build uses Edge Runtime
Development Scenario
Add Runtime Environment Comment Marker @CF_EDGE_RUNTIME
First, comment out all edge declarations in the code, use // @CF_EDGE_RUNTIME to comment. This gives this line a marker for convenient scanning and processing. After commenting out all, local development can directly use sqlite without errors
// src/app/api/random/route.ts
import { NextResponse } from 'next/server'
import { getDb, sites } from '@/db'
// @CF_EDGE_RUNTIME export const runtime = 'edge'
export async function GET() {
const db = getDb()
// ...
}
Build Scenario
- Add a list file for page routes or APIs that need to be processed. This serves as an entry point for script processing
// .edge-runtime-files.json
[
"src/app/[locale]/page.js",
"src/app/[locale]/site/[slug]/page.js",
"src/app/api/random/route.js",
"src/app/api/site/[abbr]/route.js"
]
- Add build scripts, add two scripts as pre and post build hook scripts
{
"scripts": {
"precf:build": "node scripts/manage-edge-runtime.js uncomment",
"cf:build": "npm run content:full && npx @cloudflare/next-on-pages",
"postcf:build": "node scripts/manage-edge-runtime.js comment"
}
}
I won't post the specific script code. You can just let AI write one directly
precf:build: Uncomment Edge Runtime declarationspostcf:build: Comment out Edge Runtime declarations
- sqlite3 dynamic import issue
Because sqlite3 depends on node's fs and other local file APIs, if you directly import libraries like better-sqlite3, it will cause errors during development
Use /* webpackIgnore: true */ to avoid analyzing modules during packaging, load dynamically at runtime:
const betterSqlite3Module = await import(/* webpackIgnore: true */ 'better-sqlite3')
const drizzleSqliteModule = await import(/* webpackIgnore: true */ 'drizzle-orm/better-sqlite3')
Summary
Implementation Effect
Development environment, local SQLite, zero configuration, hot reload; compile time switches node environment to edge environment, restore after compilation completion, production uses CF's D1 database
Key Points
- Edge Runtime Management: Local Node, Deploy Edge
- Dynamic Import:
/* webpackIgnore: true */avoids packaging issues
Leave a Comment
No comments yet. Be the first to share your thoughts!


