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:
- β‘ Ultra-low latency - Deploy to 300+ locations worldwide
- π° Generous free tiers - Perfect for MVPs and side projects
- π Effortless scaling - From zero to millions of users automatically
- π Built-in security - DDoS protection, WAF, and more
- π Global by default - Your app runs close to every user
Combined with Next.js 15, you get modern React features, Server Components, and Server Actions for optimal performance and developer experience.
- βοΈ Next.js 15 - App Router with React Server Components (RSC)
- π¨ TailwindCSS 4 - Utility-first CSS framework
- π TypeScript - Full type safety throughout
- π§© Shadcn UI - Unstyled, accessible components
- π React Hook Form + Zod - Type-safe form handling
- π Cloudflare Workers - Serverless edge compute platform
- ποΈ Cloudflare D1 - Distributed SQLite database at the edge
- π¦ Cloudflare R2 - S3-compatible object storage
- π€ Cloudflare Workers AI - Edge AI inference with OpenSource models
- π Better Auth - Modern authentication with Google OAuth
- π οΈ Drizzle ORM - TypeScript-first database toolkit
- βοΈ GitHub Actions - Automated CI/CD pipeline
- π§ Wrangler - Cloudflare's CLI tool
- ποΈ Preview Deployments - Test changes before production
- π Database Migrations - Version-controlled schema changes
- πΎ Automated Backups - Production database safety
- Fetching: Server Actions + React Server Components for optimal performance
- Mutations: Server Actions with automatic revalidation
- AI Processing: Edge AI inference with Cloudflare Workers AI
- Type Safety: End-to-end TypeScript from database to UI
- Caching: Built-in Next.js caching with Cloudflare edge caching
This template uses a feature-based/module-sliced architecture for better maintainability and scalability:
src/
βββ app/ # Next.js App Router
β βββ (auth)/ # Auth-related pages
β βββ api/ # API routes (for external access)
β β βββ summarize/ # AI summarization endpoint
β βββ dashboard/ # Dashboard pages
β βββ globals.css # Global styles
βββ components/ # Shared UI components
βββ constants/ # App constants
βββ db/ # Database configuration
β βββ index.ts # DB connection
β βββ schema.ts # Database schemas
βββ lib/ # Shared utilities
βββ modules/ # Feature modules
β βββ auth/ # Authentication module
β β βββ actions/ # Auth server actions
β β βββ components/ # Auth components
β β βββ hooks/ # Auth hooks
β β βββ models/ # Auth models
β β βββ schemas/ # Auth schemas
β β βββ utils/ # Auth utilities
β βββ dashboard/ # Dashboard module
β βββ todos/ # Todo module
β βββ actions/ # Todo server actions
β βββ components/ # Todo components
β βββ models/ # Todo models
β βββ schemas/ # Todo schemas
βββ services/ # Business logic services
β βββ summarizer.service.ts # AI summarization service
βββ drizzle/ # Database migrations
Key Architecture Benefits:
- Feature Isolation - Each module contains its own actions, components, and logic
- Server Actions - Modern data mutations with automatic revalidation
- React Server Components - Optimal performance with server-side rendering
- Type Safety - End-to-end TypeScript from database to UI
- Testable - Clear separation of concerns makes testing easier
- Cloudflare Account - Sign up for free
- Node.js 20+ and pnpm installed
- Google OAuth App - For authentication setup
Create an API token for Wrangler authentication:
- In the Cloudflare dashboard, go to the Account API tokens page
- Select Create Token > find Edit Cloudflare Workers > select Use Template
- Customize your token name (e.g., "Next.js Cloudflare Template")
- Scope your token to your account and zones (if using custom domains)
- Add additional permissions for D1 database and AI access:
- Account - D1:Edit
- Account - D1:Read
- Account - Cloudflare Workers AI:Read
Final Token Permissions:
- All permissions from "Edit Cloudflare Workers" template
- Account - D1:Edit (for database operations)
- Account - D1:Read (for database queries)
- Account - Cloudflare Workers AI:Read (for AI inference)
# 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:
- Create a Google OAuth 2.0 application
- Get your Client ID and Client Secret
- Add authorized redirect URIs
Understanding R2 URLs: Cloudflare R2 has different URL behaviors for development vs production:
- Development: R2 provides a public URL that works immediately
- Production: R2 public URLs are not meant for production use - you should set up a custom domain
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:
- Go to Cloudflare Dashboard
- Click "R2 Object Storage" in the sidebar
- Select your bucket from the list
- Go to the "Settings" tab
- Find "Public Development URL" section
- Click "Enable Public URL"
- Copy the displayed URL (it will be in format:
https://pub-xxxxx.r2.dev)
Example URL Format:
https://pub-a1b2c3d4e5f6g7h8i9j0.r2.dev
Important Notes:
- The URL is automatically generated by Cloudflare
- No account ID needed - it's all in the provided URL
- This URL is for development only (not production)
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):
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:
- Development: Use URL from R2 bucket "Public Development URL" setting
- Production: Must use custom domain for better performance and reliability
How R2 URLs Work:
- Base URL: Get from R2 bucket settings (format:
https://pub-xxxxx.r2.dev) - File URLs:
https://pub-xxxxx.r2.dev/{folder}/{file-name}.{extension} - Environment Variable: Only the base URL goes into
CLOUDFLARE_R2_URL - Code: The full file paths are constructed programmatically using the base URL
If 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:
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:
- Go to Google Cloud Console
- Create a new project or select existing one
- Enable Google+ API
- Create OAuth 2.0 credentials
- Add authorized redirect URIs:
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:
- Open
http://localhost:3000 - Test authentication flow
- Create a todo item
- Check database:
pnpm run db:studio:local
Add Repository Secrets: Go to your GitHub repository β Settings β Secrets and add:
CLOUDFLARE_API_TOKEN- Your API token from Step 2CLOUDFLARE_ACCOUNT_ID- Your account IDBETTER_AUTH_SECRET- Your auth secretGOOGLE_CLIENT_ID- Your Google client IDGOOGLE_CLIENT_SECRET- Your Google client secretCLOUDFLARE_R2_URL- Your R2 bucket URL
Deploy Production Database:
# Apply migrations to production
pnpm run db:migrate:prod
# Verify production database
pnpm run db:inspect:prodAdd Custom Domain:
- Go to Cloudflare dashboard β Workers & Pages
- Select your worker β Settings β Triggers
- Click "Add Custom Domain"
- Enter your domain (must be in your Cloudflare account)
Update OAuth Redirect URLs: Add your custom domain to Google OAuth settings:
https://yourdomain.com/api/auth/callback/google
Add Indexes for Performance:
-- Create indexes for better query performance
CREATE INDEX IF NOT EXISTS idx_todos_user_id ON todos(user_id);
CREATE INDEX IF NOT EXISTS idx_todos_created_at ON todos(created_at);
CREATE INDEX IF NOT EXISTS idx_todos_completed ON todos(completed);Monitor Database Performance:
# View database insights
wrangler d1 insights your-app-name --since 1h
# Export data for analysis
wrangler d1 export your-app-name --output backup.sqlConfigure CORS for Direct Uploads:
# Create CORS policy file
echo '[
{
"AllowedOrigins": ["https://yourdomain.com", "http://localhost:3000"],
"AllowedMethods": ["GET", "PUT", "POST", "DELETE"],
"AllowedHeaders": ["*"],
"ExposeHeaders": [],
"MaxAgeSeconds": 3000
}
]' > cors.json
# Apply CORS policy
wrangler r2 bucket cors put your-app-bucket --file cors.json# 1. Generate Cloudflare types (run after any wrangler.jsonc changes)
pnpm run cf-typegen
# 2. Apply database migrations
pnpm run db:migrate:local
# 3. Build the application for Cloudflare
pnpm run build:cf# Terminal 1: Start Wrangler for D1 database access
pnpm run wrangler:dev
# Terminal 2: Start Next.js development server with HMR
pnpm run devDevelopment URLs:
- π Next.js with HMR:
http://localhost:3000(recommended) - βοΈ Wrangler Dev Server:
http://localhost:8787
# Single command - Cloudflare runtime (no HMR)
pnpm run dev:cf
# Test with remote Cloudflare resources
pnpm run dev:remote| Script | Description |
|---|---|
pnpm dev |
Start Next.js with HMR |
pnpm run build:cf |
Build for Cloudflare Workers |
pnpm run wrangler:dev |
Start Wrangler for local D1 access |
pnpm run dev:cf |
Combined build + Cloudflare dev server |
| Script | Description |
|---|---|
pnpm run db:generate |
Generate new migration |
pnpm run db:generate:named "migration_name" |
Generate named migration |
pnpm run db:migrate:local |
Apply migrations to local D1 |
pnpm run db:migrate:preview |
Apply migrations to preview |
pnpm run db:migrate:prod |
Apply migrations to production |
pnpm run db:studio:local |
Open Drizzle Studio for local DB |
pnpm run db:inspect:local |
List local database tables |
pnpm run db:reset:local |
Reset local database |
| Script | Description |
|---|---|
pnpm run deploy |
Deploy to production |
pnpm run deploy:preview |
Deploy to preview environment |
pnpm run cf-typegen |
Generate Cloudflare TypeScript types |
pnpm run cf:secret |
Add secrets to Cloudflare Workers |
First-time setup:
pnpm run cf-typegen- Generate typespnpm run db:migrate:local- Setup databasepnpm run build:cf- Build application
Daily development:
pnpm run wrangler:dev- Start D1 access (Terminal 1)pnpm run dev- Start Next.js with HMR (Terminal 2)
After schema changes:
pnpm run db:generate- Generate migrationpnpm run db:migrate:local- Apply to local DB
After wrangler.jsonc changes:
pnpm run cf-typegen- Regenerate types
Browser Console (Easiest):
- Login at
http://localhost:3000 - Open DevTools Console (F12)
- Run:
fetch('/api/summarize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
text: "Your text to summarize here...",
config: { maxLength: 100, style: "concise" }
})
}).then(r => r.json()).then(console.log);cURL (with session cookies):
- Login in browser first
- DevTools β Application β Cookies β Copy
better-auth.session_token - Use cookie in cURL:
curl -X POST http://localhost:3000/api/summarize \
-H "Content-Type: application/json" \
-H "Cookie: better-auth.session_token=your-token-here" \
-d '{"text": "Your text here...", "config": {"maxLength": 100}}'Postman:
- Login in browser, copy session cookie from DevTools
- Add header:
Cookie: better-auth.session_token=your-token-here
Unauthenticated Request Response:
{
"success": false,
"error": "Authentication required",
"data": null
}The AI integration follows a clean service-based architecture:
- API Route (
/api/summarize) - Handles HTTP requests, authentication, and validation - Authentication Layer - Validates user session before processing requests
- SummarizerService - Encapsulates AI business logic
- Error Handling - Comprehensive error responses with proper status codes
- Type Safety - Full TypeScript support with Zod validation
Cloudflare Workers AI supports various models:
- @cf/meta/llama-3.2-1b-instruct - Text generation (current)
- @cf/meta/llama-3.2-3b-instruct - More capable text generation
- @cf/meta/m2m100-1.2b - Translation
- @cf/baai/bge-base-en-v1.5 - Text embeddings
- @cf/microsoft/resnet-50 - Image classification
# 1. Modify schema files in src/db/schemas/
# 2. Generate migration
pnpm run db:generate:named "add_user_table"
# 3. Apply to local database
pnpm run db:migrate:local
# 4. Test your changes
# 5. Commit and deploy (migrations run automatically)# 1. Update wrangler.jsonc with new resources
# 2. Regenerate types
pnpm run cf-typegen
# 3. Update your code to use new bindings# Add secrets to production environment
pnpm run cf:secret BETTER_AUTH_SECRET
pnpm run cf:secret GOOGLE_CLIENT_ID
pnpm run cf:secret GOOGLE_CLIENT_SECRETBuilt-in Observability:
- β Cloudflare Analytics (enabled by default)
- β Real User Monitoring (RUM)
- β Error tracking and logging
- β Performance metrics
Database Monitoring:
# Monitor database performance
wrangler d1 insights next-cf-app
# View database metrics in Cloudflare Dashboard
# Navigate to Workers & Pages β D1 β next-cf-app β MetricsPush to main branch triggers automatic deployment via GitHub Actions:
git add .
git commit -m "feat: add new feature"
git push origin mainDeployment Pipeline:
- β Install dependencies
- β Build application
- β Run database migrations
- β Deploy to Cloudflare Workers
# Deploy to production
pnpm run deploy
# Deploy to preview environment
pnpm run deploy:preview- Add text translation service with
@cf/meta/m2m100-1.2b - Implement text embeddings for semantic search with
@cf/baai/bge-base-en-v1.5 - Add image classification API with
@cf/microsoft/resnet-50 - Create chat/conversation API with conversation memory
- Add content moderation with AI classification
- Implement sentiment analysis for user feedback
- Implement email sending with Resend & Cloudflare Email Routing
- Implement international payment gateway with Polar.sh
- Implement Indonesian payment gateway either with Xendit, Midtrans, or Duitku
- Add Cloudflare Analytics integration
- Implement custom metrics tracking
- Add performance monitoring dashboard
- Create AI usage analytics and cost tracking
Contributions are welcome! Please feel free to submit issues and pull requests.
This project is licensed under the MIT License - see the LICENSE file for details.
Β© 2025 Muhammad Arifin. All rights reserved.
{ "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" } }