Full-stack Next.js 15 + Cloudflare Workers template with D1 database, R2 storage, Better Auth, and Server Actions. Production-ready with automated CI/CD and
Full-stack Next.js 15 + Cloudflare Workers template with D1 database, R2 storage, Better Auth, and Server Actions. Production-ready with automated CI/CD and
A production-ready template for building full-stack applications with Next.js 15 and Cloudflare's powerful edge infrastructure. Perfect for MVPs with generous free tiers and seamless scaling to enterprise-level applications.
Inspired by the Cloudflare SaaS Stack - the same stack powering Supermemory.ai, which serves 20k+ users on just $5/month. This template modernizes that approach with Cloudflare Workers (vs Pages), includes comprehensive D1 and R2 examples, and provides a complete development workflow.
You can read detail explanations and code architecture of this template from Devin AI on Deepwiki.
Don't forget to leave a star if you find this helpful ⭐️
Cloudflare's Edge Network provides unparalleled performance and reliability:
Combined with Next.js 15, you get modern React features, Server Components, and Server Actions for optimal performance and developer experience.
This template uses a feature-based/module-sliced architecture for better maintainability and scalability:
…Key Architecture Benefits:
Create an API token for Wrangler authentication:
Final Token Permissions:
# Clone the repository
git clone https://github.com/ifindev/fullstack-next-cloudflare.git
cd fullstack-next-cloudflare
# Install dependencies
pnpm installCreate your environment file:
# Copy example environment file
cp .dev.vars.example .dev.varsEdit .dev.vars with your credentials:
# Cloudflare Configuration
CLOUDFLARE_ACCOUNT_ID=your-account-id
CLOUDFLARE_D1_TOKEN=your-api-token
# Authentication Secrets
BETTER_AUTH_SECRET=your-random-secret-here
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
# Storage
CLOUDFLARE_R2_URL=your-r2-bucket-urlBetter Auth Secret:
# Generate a random secret
openssl rand -base64 32
# Add to BETTER_AUTH_SECRET in .dev.varsGoogle OAuth Setup: Follow the Better Auth Google documentation to:
Understanding R2 URLs: Cloudflare R2 has different URL behaviors for development vs production:
Step 1: Create R2 Bucket
# Create R2 bucket for development
wrangler r2 bucket create your-app-bucket-dev
# For production, create a separate bucket
wrangler r2 bucket create your-app-bucket-prodStep 2: Get R2 URL for Development
Enable Public Development URL:
https://pub-xxxxx.r2.dev)Example URL Format:
https://pub-a1b2c3d4e5f6g7h8i9j0.r2.devImportant Notes:
Step 3: Add R2 URL to Environment Variables
# Add to your .dev.vars file (use the URL you copied from dashboard)
CLOUDFLARE_R2_URL=https://pub-a1b2c3d4e5f6g7h8i9j0.r2.devNote: Replace the example URL with the actual URL you copied from your R2 bucket settings.
For Production - Custom Domain Setup (Required):
⚠️ Important: The default R2 public URL should NOT be used in production as it's not optimized for performance and may have limitations.
Setup Custom Domain for R2:
# 1. Go to Cloudflare Dashboard → R2 Storage → Your Bucket → Custom Domains
# 2. Click "Connect Domain" and enter your desired domain (e.g., files.yourdomain.com)
# 3. Update your DNS records as instructed by Cloudflare
# 4. Wait for SSL certificate to be issued (usually a few minutes)
# Your production R2 URL will be:
# https://files.yourdomain.com
# Add this to your production secrets:
echo "https://files.yourdomain.com" | wrangler secret put CLOUDFLARE_R2_URLR2 URL Summary:
How R2 URLs Work:
https://pub-xxxxx.r2.dev)https://pub-xxxxx.r2.dev/{folder}/{file-name}.{extension}CLOUDFLARE_R2_URLIf you prefer to set everything up manually or want to understand each step in detail, follow this comprehensive guide.
Create D1 Database:
# Create a new SQLite database at the edge
wrangler d1 create your-app-name
# Output will show:
# database_name = "your-app-name"
# database_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"Create R2 Bucket:
# Create object storage bucket
wrangler r2 bucket create your-app-bucket
# List buckets to confirm
wrangler r2 bucket listUpdate wrangler.jsonc with your resource IDs:
{
"name": "your-app-name",
"d1_databases": [
{
"binding": "DB",
"database_name": "your-app-name",
"database_id": "your-database-id-from-step-1",
"migrations_dir": "./src/drizzle"
}
],
"r2_buckets": [
{
"bucket_name": "your-app-bucket",
"binding": "FILES"
}
],
"ai": {
"binding": "AI"
}
}Generate Better Auth Secret:
# On macOS/Linux
openssl rand -base64 32
# On Windows (PowerShell)
[System.Convert]::ToBase64String([System.Security.Cryptography.RandomNumberGenerator]::GetBytes(32))
# Or use online generator: https://generate-secret.vercel.app/32Configure Google OAuth:
http://localhost:3000/api/auth/callback/google (development)https://your-app.your-subdomain.workers.dev/api/auth/callback/google (production)Create Local Environment File:
# .dev.vars for local development
CLOUDFLARE_ACCOUNT_ID=your-account-id
CLOUDFLARE_D1_TOKEN=your-api-token
BETTER_AUTH_SECRET=your-generated-secret
GOOGLE_CLIENT_ID=your-google-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-google-client-secret
# Get this from R2 bucket settings: R2 Object Storage → Your Bucket → Settings → Public Development URL
CLOUDFLARE_R2_URL=https://pub-a1b2c3d4e5f6g7h8i9j0.r2.devSet Production Secrets:
# Add each secret to Cloudflare Workers
echo "your-secret-here" | wrangler secret put BETTER_AUTH_SECRET
echo "your-client-id" | wrangler secret put GOOGLE_CLIENT_ID
echo "your-client-secret" | wrangler secret put GOOGLE_CLIENT_SECRET
echo "your-r2-url" | wrangler secret put CLOUDFLARE_R2_URLGenerate TypeScript Types:
# Generate Cloudflare bindings for TypeScript
pnpm run cf-typegenInitialize Database:
# Generate initial migration from schema
pnpm run db:generate
# Apply migrations to local database
pnpm run db:migrate:local
# Verify database structure
pnpm run db:inspect:localOptional: Seed Sample Data
# Create and run a seed script
wrangler d1 execute your-app-name --local --command="
INSERT INTO todos (id, title, description, completed, created_at, updated_at) VALUES
('1', 'Welcome to your app', 'This is a sample todo item', false, datetime('now'), datetime('now')),
('2', 'Set up authentication', 'Configure Google OAuth', true, datetime('now'), datetime('now'));
"Start Development Servers:
# Terminal 1: Start Wrangler (provides D1 access)
pnpm run wrangler:dev
# Terminal 2: Start Next.js (provides HMR)
pnpm run dev
# Alternative: Single command (no HMR)
pnpm run dev:cfVerify Everything Works:
http://localhost:3000pnpm run db:studio:localNo open issues yet, or sync has not completed.