Production-ready boilerplate for building edge-deployed Next.js applications with programmatic SEO and passwordless authentication.
| Technology | Purpose | Why |
|---|---|---|
| Next.js 15 | Framework | App Router, Server Components, SSG |
| Cloudflare Workers | Runtime | Global edge, <50ms latency, $0-5/month |
| OpenNext | Adapter | Next.js → Workers transpilation |
| D1 | Database | Edge SQLite, <1ms queries |
| Drizzle ORM | ORM | Type-safe, edge-compatible |
| Better Auth | Auth | Passwordless, magic links, edge-native |
| Resend | Reliable delivery, great DX |
- Edge-First: Deployed globally on Cloudflare's 275+ locations
- Database-Driven pSEO: Activate pages instantly without redeployment
- Passwordless Auth: Magic links via email
- Background Jobs: Database-backed job queue with cron triggers
- Type-Safe: Full TypeScript with Drizzle ORM inference
- Cost-Effective: Free tier covers startup scale ($0-6/month)
| ADR | Title |
|---|---|
| 001 | Next.js 15 with App Router |
| 002 | Cloudflare Workers Deployment |
| 003 | D1 Database with Drizzle ORM |
| 004 | pSEO Database-Driven Rollout |
| 005 | Custom Worker Wrapper |
| 006 | Better Auth Authentication |
| 007 | Database-Backed Background Job System |
- Boilerplate Specification - Complete reference
node -v # 20+
pnpm -v # 8+# Clone/copy this boilerplate
cd your-project-name
# Install dependencies
pnpm install
# Create environment file
cp .env.example .env.local
# Create D1 database
npx wrangler d1 create your-db-name
# Update wrangler.toml with database_id
# Run migrations
for f in migrations/*.sql; do
npx wrangler d1 execute your-db --remote --file "$f"
done
# Start development
pnpm dev# Build
pnpm build
# Deploy to Cloudflare
npx wrangler deployUser Request
↓
Cloudflare Edge (275+ locations)
↓
worker.js (Custom Wrapper)
↓ pSEO check: D1 query (<1ms)
↓
OpenNext Worker
↓
Next.js App
↓
D1 Database
// worker.js
if (isPSEORoute(pathname)) {
const status = await checkPageStatus(env.DB, pathname);
if (status === 'blocked') {
return new Response('Not Found', { status: 404 });
}
}
return openNextWorker.fetch(request, env, ctx);-- Instantly activate pages (no redeploy)
UPDATE pseo_pages
SET status = 'active', published_at = unixepoch()
WHERE sitemap_batch = 1;// Better Auth with D1
const auth = betterAuth({
database: drizzleAdapter(db, { provider: 'sqlite' }),
plugins: [magicLink({ /* ... */ })],
});// Cron trigger processes pending jobs
export default {
async scheduled(event, env, ctx) {
const result = await processJobs(db, env);
console.log(`Processed: ${result.processed}`);
},
};
// Job handler pattern
const jobHandlers = {
document_processing: handleDocumentProcessing,
email_notification: handleEmailNotification,
data_sync: handleDataSync,
};- Workers: 100k requests/day
- D1: 5M reads/day
- Resend: 3k emails/month
- Total: $0/month
- Workers: $5/month
- D1: $1/month
- Resend: $0-20/month
- Total: ~$6-26/month
boilerplate/
├── docs/ # All documentation
│ ├── adr/ # Architecture decisions (7 ADRs)
│ ├── technical/ # Technical docs
│ ├── guides/ # How-to guides
│ └── BOILERPLATE_SPEC.md
├── migrations/ # SQL migrations (5 files)
│ ├── 0001_initial.sql # Core tables
│ ├── 0002_seed_data.sql # Seed data
│ ├── 0003_pseo_tables.sql
│ ├── 0004_auth.sql
│ └── 0005_background_jobs.sql
├── .claude/ # Claude Code config
│ ├── CLAUDE.md # Project instructions
│ ├── memories/ # Persistent context
│ └── skills/ # Specialized skills
├── worker.js # Custom Worker wrapper
├── wrangler.toml # Cloudflare config
└── README.md # This file
- Update
wrangler.tomlwith your app name - Create D1 database and update
database_id - Configure environment variables
- Customize database schema for your domain
- Update pSEO clusters for your content
- Configure email sender in Resend
- Setup custom domain
MIT