Background
Next.js + Cloudflare Deployment: Production D1 and Local SQLite Database Solution

Next.js + Cloudflare Deployment: Production D1 and Local SQLite Database Solution

February 1, 20264603 words4 min read
Cayden
AuthorCayden

Focused on independent product development, recording bits and pieces of the development process

Welcome to follow WeChat Official Account CayDock

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

D1Supabase
LatencySame network as Pages, zero latencyNeeds to route to central database, has network hops
Free Tier25,000 reads/day, 5GB storage500MB storage, 500MB transfer
Overage Cost$0.001/thousand reads$25/month starting
DeploymentIntegrated with CloudflareIndependent deployment

Why Not Use D1 for Local Development?

The development experience of wrangler pages dev is poor:

  1. No Hot Reload: After modifications, you need to rebuild (npm run cf:build && npm run cf:preview)
  2. Difficult Debugging: Edge Runtime breakpoints are not as comprehensive as Node.js
  3. 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

  1. 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"
]
  1. 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 declarations
  • postcf:build: Comment out Edge Runtime declarations
  1. 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

  1. Edge Runtime Management: Local Node, Deploy Edge
  2. Dynamic Import: /* webpackIgnore: true */ avoids packaging issues
Share:

Leave a Comment

Minimum 3 charactersYour email will not be published. Required fields are marked with *

No comments yet. Be the first to share your thoughts!