Cloudflare as the runtime platform: Workers compute, D1 database, KV cache, R2 storage, Queues, and Email Routing
saas_template — Next.js SaaS Template
By omar16100
A Cloudflare-oriented SaaS starter template built on the Next.js App Router, integrating D1, Better Auth, and Stripe, with marketing pages, authentication, dashboard, and SEO infrastructure.
Explore the design
Compare the page layouts and mobile experience. Open any screenshot for a closer look.
Actual screenshot captured by the server browser.
All pages & views 2
Screenshots show visual appearance; interactive behavior may differ.
What stands out
A quick read of the recorded project description.
Better Auth provides email/password, Google OAuth, magic links, Passkeys, and 2FA
Stripe-hosted Checkout and Customer Portal, with webhooks using D1 for idempotent records
SEO infrastructure: dynamic sitemap, robots, RSS, JSON-LD, IndexNow, llms.txt, and noindex for preview environments
Secure defaults: per-route CSP, HSTS, X-Frame-Options DENY, Referrer-Policy, and Permissions-Policy
Repository layer db/repo/* isolates D1, making it easier to swap databases later without changing domain code
Template Details
This is a SaaS starter template that treats Cloudflare as its primary runtime platform; the repository is MIT-licensed. The stack centers on the Next.js App Router, deployed to Cloudflare Workers via @opennextjs/cloudflare, with D1 and Drizzle for data, Better Auth for authentication, and Stripe-hosted Checkout and Customer Portal for payments. Rendering is split by route: marketing pages and blog use SSG/ISR for SEO, authentication and dashboard use SSR, and API routes run on Workers. The repository is a flat Next.js application, not a monorepo.
The template includes page structures for marketing, pricing, blog, legal pages, sign-in/sign-up and password reset, and an authenticated dashboard, along with a dynamic sitemap, robots, RSS, JSON-LD, an IndexNow endpoint, llms.txt, and consent-gated GA4 and PostHog analytics. On security, it provides per-route CSP, strict security headers, idempotent D1 records for Stripe webhooks, and account deletion plus data export to R2.
Note that the README explicitly lists several modules that are not yet wired end to end: the Turnstile component renders but no route calls the verification function, rate-limiting utilities exist but are not connected to routes, account deletion only enqueues with no consumer processing it, the root layout canonical affects pages that do not set their own canonical, and billing has inconsistent price variable naming, portal redirect, and webhook retry logic issues. The template intentionally excludes multi-tenancy, i18n, Storybook, status pages, and newsletters.
Is it right for your project?
Use Cases
- Starting a SaaS project that needs Cloudflare Workers and D1 as its runtime foundation
- A Next.js application where marketing pages and blog use SSG/ISR while authentication and dashboard use SSR
- A starter template with built-in authentication, subscription billing, email templates, and SEO metadata
- A deployment approach that uses compute, database, object storage, queues, and email routing within the same platform
Suitable For
- Developers familiar with the Next.js App Router and the Cloudflare ecosystem
- SaaS product prototypes or early-stage projects with a single-user model
- Teams that accept the template's opinionated structure and are willing to replace components as needed
- Scenarios that need an SEO-friendly marketing site alongside a protected dashboard
Getting started & customization
Use these recorded setup notes alongside the repository instructions for your chosen version.
- 1
After cloning, run pnpm install, copy .env.example to .env.local, and at minimum generate BETTER_AUTH_SECRET
- 2
Use wrangler to create D1, KV, R2, and Queues resources, and fill the returned IDs into the REPLACE_ME placeholders in wrangler.toml
- 3
Configure BETTER_AUTH_SECRET, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, RESEND_API_KEY, and TURNSTILE_SECRET_KEY per environment via wrangler secret put
- 4
NEXT_PUBLIC_* variables are not secrets and must be set in the build environment; vars in wrangler.toml only override runtime reads
- 5
Before deployment, complete the checklist: domain routing, Resend sending domain verification, Stripe webhook registration, Turnstile site creation, and search platform verification
License & source material
Usage conditions & limitations
Free to use · keep license noticeThis entry covers the free version in the public repository under the MIT license; the free scope is that version's code and does not imply that external services or third-party assets are free.
Must comply with when using
- You may use, modify, commercialize, and distribute this version's code for free.
- When distributing copies or substantial portions of the code, retain the original author's copyright notice and the full MIT license notice.
- The code is provided as is; warranties and limitations of liability are governed by the original license and applicable law.
Additional costs & items to verify
- The template requires you to configure credentials to run fully: authentication needs BETTER_AUTH_SECRET, and related features degrade when Stripe, Resend, or Turnstile are not configured.
- Deployment and resource creation require a Cloudflare account and wrangler login, which are external service dependencies.
- Deployment requires connecting a domain to Cloudflare; local development does not require it.
- Email sending depends on the Resend service, requiring a verified sending domain and DKIM, SPF, and DMARC configuration.
- Billing depends on Stripe, requiring webhook registration per environment and a signing secret.
- Human verification depends on Cloudflare Turnstile, requiring site creation and key configuration.
- Log export depends on the Axiom service, requiring a dataset and token plus Logpush configuration.
- Analytics depends on PostHog and GA4, requiring project and property creation and ID configuration.
- Search platform verification requires registering the site with Google Search Console and Bing Webmaster Tools.
- IndexNow requires generating a key file yourself and placing it in the public directory.
- Turnstile only renders the component; no route calls the verification function, so protection is not wired end to end.
- Rate-limiting utilities exist but are not connected to any route, so they are not actually in effect.
- Account deletion only enqueues, with no queue consumer processing it, so the deletion flow is incomplete.
- The root layout canonical points to the homepage, so pages that do not set their own canonical are pointed to the homepage by search engines.
- Billing has defects: inconsistent price variable naming, portal redirect not implemented, and webhook failures are not retried.
- Demo images, fonts, icons, and other third-party assets must be checked against their respective licenses; the code's MIT license alone cannot be used to judge them.
- External services such as domains, hosting, databases, payments, or AI may incur separate charges.
View Original Text Basis
Permission is hereby granted, free of charge
Original License Text
The app boots with degraded features until you fill in credentials (auth needs the secret; Stripe/Resend/Turnstile each fail gracefully if unconfigured).
Original README
a Cloudflare account · `wrangler login`
Original README
Domain added to Cloudflare (can be a subdomain); only required for deploy, not local dev
Original README
Verify your sending domain in **Resend** (DKIM + SPF + DMARC)
Original README
Register a **Stripe** webhook endpoint per environment → paste signing secret
Original README
Create **Turnstile** site → paste keys
Original README
Create **Axiom** dataset + token → set up Cloudflare Logpush job → Axiom
Original README
Create **PostHog** project + **GA4** property → paste IDs
Original README
Add site to **Google Search Console** and **Bing Webmaster Tools** → paste verification tokens
Original README
Generate `INDEXNOW_KEY` → serve at `/{key}.txt` under `public/`
Original README
Turnstile: the widget renders on sign-in/sign-up, but no route calls `verifyTurnstile` in `lib/turnstile.ts`.
Original README
Rate limiting: `lib/rate-limit.ts` exists, but no route calls it.
Original README
Account deletion enqueues a purge job, but no queue consumer processes it.
Original README
The root layout sets canonical `/`, so pages without their own `alternates.canonical` (pricing, blog index, auth pages) point search engines at the homepage.
Original README
https://github.com/omar16100/saas_template/blob/main/LICENSEhttps://github.com/omar16100/saas_template/blob/main/README.mdBilling: the pricing button reads `NEXT_PUBLIC_STRIPE_PRICE_*` while `.env.example` defines `STRIPE_PRICE_*`; the dashboard "Manage billing" form receives the portal URL as JSON instead of being redirected; the webhook records an event before applying it, so a failed apply is not retried.
Original README
Compiled by DeepSeek based on repository license and README ·2026-10-02. Results only cover the materials read; dependencies, images, fonts, and subsequent term changes are subject to their respective original texts.
Project checks & description sources
Template Information
- Source platforms
- GitHub
- Documentation and maintenance reference score
- More documented · 88 / 100
- Technologies
- Next.js · React
- Website type
- SaaS · Starter
How this score is calculated
Our heuristic uses license records (up to 30), a demo link (12), a preview (10), stars (15), recorded maintenance (15), description completeness (8), feature records (5) and classification data (5). It is not a code, security or performance test, or a Google score. A link does not establish tested functionality.
What to verify for this project
- Repository update recorded
- 2026-09-27
- Screenshot last checked
- 2026-10-02
A demo link is listed. Check it with your own content and viewport sizes; its presence does not confirm that forms, authentication or payments work.
The catalog identifies Next.js as the primary stack. Check the actual dependency manifest and lockfile for versions and hosting requirements. These notes use recorded metadata, not a code audit or a deployment test.
Use a selection guide →How this description was prepared
AI-assisted description based on catalog records and repository material. Repository authors are not automatically the reviewers of this description.
Automatically checked against source and license rules; no manual editorial review.
Inspect the recorded source material
SHA-256: fe7ba73becb456ad7620e8b5acc049bc8d0d7ba0f44c92c5f6ad81c923aca0a8
2026-10-02T01:00:59.807Z
Source file / repository# SaaS Template
[](https://omar16100.github.io/saas_template/)
[](LICENSE)
**Website:** https://omar16100.github.io/saas_template/
Opinionated, Cloudflare-first SaaS starter. Clone, rename, deploy.
**Stack:** Next.js 16 (App Router) on Cloudflare Workers via `@opennextjs/cloudflare` · D1 + Drizzle · Better Auth (passkeys, MFA, OAuth, magic links) · Stripe hosted Checkout + Portal · Resend + CF Email Routing · shadcn/ui + Tailwind · GA4 + PostHog (consent-gated) · Turnstile · Axiom via Logpush · Vitest + Playwright · GitHub Actions CI + opt-in Cloudflare deploy.
**Rendering:** Marketing + blog → SSG/ISR (edge-cached, best SEO). Auth + dashboard → SSR. API routes → Workers.
---
## Why this template
- **One platform.** Every runtime dependency (compute, DB, files, cache, queues, email routing) lives on Cloudflare. One bill, one dashboard, one set of credentials.
- **SEO-first.** Static marketing + blog, dynamic sitemap, JSON-LD, IndexNow, `llms.txt`, preview auto-`noindex`, Lighthouse report on every PR (non-blocking).
- **Secure by default.** Route-scoped CSP, strict security headers, D1-backed Stripe webhook idempotency, Better Auth with passkeys, Turnstile widget and D1 rate-limit helpers, account delete + data export (see [Known gaps](#known-gaps)).
- **Boring, swappable stack.** Repository layer (`db/repo/*`) isolates D1 so you can swap to Postgres/Turso later without touching domain code.
- **No monorepo tax.** Flat Next.js app. Promote to a workspace later only if you genuinely need to.
---
## Quick start
```bash
git clone https://github.com/omar16100/saas_template.git my-saas
cd my-saas
pnpm install
cp .env.example .env.local
# fill in BETTER_AUTH_SECRET at minimum: openssl rand -base64 32
pnpm dev
```
Visit `http://localhost:3000`.
The app boots with degraded features until you fill in credentials (auth needs the secret; Stripe/Resend/Turnstile each fail gracefully if unconfigured).
---
## Full setup
### 1. Prereqs
- Node 22 (see `.nvmrc`) · pnpm 9 · a Cloudflare account · `wrangler login`
- Domain added to Cloudflare (can be a subdomain); only required for deploy, not local dev
### 2. Install
```bash
pnpm install
cp .env.example .env.local
```
### 3. Local secret
```bash
# Generate a 32+ char secret for Better Auth
openssl rand -base64 32
# paste into .env.local → BETTER_AUTH_SECRET
```
### 4. Create Cloudflare bindings
```bash
# Databases
wrangler d1 create saas_db
wrangler d1 create saas_db_preview
# KV (cache)
wrangler kv namespace create CACHE
wrangler kv namespace create CACHE --preview
# R2 buckets (uploads + Next ISR cache, both envs)
wrangler r2 bucket create saas-uploads
wrangler r2 bucket create saas-uploads-preview
wrangler r2 bucket create saas-next-cache
wrangler r2 bucket create saas-next-cache-preview
# Queues (main + DLQ, both envs)
wrangler queues create saas-jobs
wrangler queues create saas-jobs-dlq
wrangler queues create saas-jobs-preview
wrangler queues create saas-jobs-preview-dlq
```
Paste the returned IDs into `wrangler.toml` wherever you see `REPLACE_ME`.
### 5. Secrets per environment
```bash
# Production
wrangler secret put BETTER_AUTH_SECRET --env production
wrangler secret put STRIPE_SECRET_KEY --env production
wrangler secret put STRIPE_WEBHOOK_SECRET --env production
wrangler secret put RESEND_API_KEY --env production
wrangler secret put TURNSTILE_SECRET_KEY --env production
# Preview (repeat with TEST keys)
wrangler secret put BETTER_AUTH_SECRET --env preview
wrangler secret put STRIPE_SECRET_KEY --env preview
# ...etc
```
Public (`NEXT_PUBLIC_*`) vars are not secrets, so never `wrangler secret` them. `next build` inlines them, so they must be set where you build (`.env.local`, your shell, or the GitHub environment used by `deploy.yml`); the `[env.*.vars]` blocks in `wrangler.toml` only cover runtime reads.
### 6. Database
```bash
pnpm db:migrate:local # apply db/migrations locally
pnpm db:migrate:preview # apply to preview D1
pnpm db:migrate:prod # apply to production D1
pnpm db:generate # after editing db/schema: write the next migration, then commit it
```
If your D1 already has tables from migrations you generated before `0000_initial.sql` was committed, keep your own `db/migrations` history and run `pnpm db:generate` instead of applying it (see `docs/27092026_auth_schema_plan.md`).
`db/schema/auth.ts` must hold every table and field the enabled Better Auth plugins write: Better Auth checks it on every auth request and fails the request on a mismatch. `tests/unit/auth-schema.test.ts` runs the same check, so adding a plugin without its tables fails `pnpm test`.
### 7. Run
```bash
pnpm dev # http://localhost:3000
pnpm build # next build
pnpm build:worker # next build + OpenNext worker bundle (.open-next/)
pnpm preview # run the built worker locally (after build:worker)
pnpm typecheck
pnpm lint
pnpm test # vitest
pnpm test:e2e # playwright
```
### 8. Deploy
```bash
pnpm deploy:preview # wrangler deploy --env preview
pnpm run deploy # wrangler deploy --env production (`pnpm deploy` is a pnpm builtin, so use `run`)
```
GitHub Actions can do this for you: `.github/workflows/deploy.yml` deploys a preview on every PR and production on merge to `main` once you add repo secrets `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID`. Create GitHub environments `preview` and `production`, each with at least the variable `NEXT_PUBLIC_APP_URL` plus any other `NEXT_PUBLIC_*` values your build needs; the workflow exports them before building and sets `NEXT_PUBLIC_IS_PREVIEW` itself. Without the secrets the deploy job is skipped and the workflow passes. Secrets are not passed to PRs from forks or Dependabot, so those PRs skip it too.
### 9. Post-deploy checklist
- [ ] Add your domain to CF and route `example.com/*` in `wrangler.toml`
- [ ] Enable **CF Email Routing** (inbound only: support@, hello@ → your inbox)
- [ ] Verify your sending domain in **Resend** (DKIM + SPF + DMARC)
- [ ] Register a **Stripe** webhook endpoint per environment → paste signing secret
- [ ] Create **Turnstile** site → paste keys
- [ ] Create **Axiom** dataset + token → set up Cloudflare Logpush job → Axiom
- [ ] Add site to **Google Search Console** and **Bing Webmaster Tools** → paste verification tokens
- [ ] Generate `INDEXNOW_KEY` → serve at `/{key}.txt` under `public/`
- [ ] Create **PostHog** project + **GA4** property → paste IDs
See `docs/setup.md` for verbose version and `todo.md` for the full one-time checklist.
---
## Environment variables
Full list in `.env.example`. Categorized in `docs/setup.md`. The minimum to boot locally: `NEXT_PUBLIC_APP_URL`, `NEXT_PUBLIC_APP_NAME`, `BETTER_AUTH_SECRET`.
---
## Project structure
```
app/
(marketing)/ # SSG/ISR: landing, pricing, blog, legal
(auth)/ # sign-in, sign-up, reset
(app)/ # SSR, authed dashboard
api/ # auth, stripe, indexnow, account, health
sitemap.ts robots.ts feed.xml/
components/ # UI + consent + analytics + JSON-LD
content/blog/ # MDX posts
db/
schema/ # Drizzle schema (auth, billing, app)
repo/ # repository layer: D1 impl today, swappable
migrations/ # SQL from `pnpm db:generate`, committed; CI fails if it is out of date
emails/ # react-email templates
lib/ # auth, db, stripe, resend, logger, csp, rate-limit, env
tests/ # unit (vitest) + e2e (playwright)
docs/ # index, c4model, setup, runbooks/, adr/
```
---
## What's included
- **Auth:** email+password, Google OAuth, magic links, passkeys, 2FA (Better Auth)
- **Billing:** Stripe hosted Checkout, Customer Portal, webhook with D1 idempotency, Stripe Tax
- **Email:** Resend outbound + react-email templates (welcome, verify, reset, magic link, deletion)
- **Bot / abuse:** Turnstile widget on sign-in/sign-up, D1 fixed-window rate limiter helper (both still to be wired into routes, see [Known gaps](#known-gaps))
- **Security:** route-scoped CSP (static for marketing = SSG-safe, nonce for app), HSTS, X-Frame-Options DENY, no-sniff, strict Referrer-Policy, Permissions-Policy
- **SEO:** dynamic sitemap, robots (preview `noindex`), RSS, JSON-LD (Organization / WebSite+SearchAction / BreadcrumbList / Article / FAQPage / SoftwareApplication), IndexNow endpoint, `llms.txt`, canonical URLs on blog posts and legal pages, consent-gated GA4 + PostHog, web-vitals → PostHog, Lighthouse report on PRs
- **Privacy:** consent banner gates analytics, account delete w/ 30-day grace (enqueued; consumer not included yet), account data export to R2
- **Ops:** structured JSON logging (ship to Axiom with Cloudflare Logpush, see `docs/setup.md`), CI (lint, typecheck, unit tests, migration generation, production build, non-blocking Lighthouse on PRs), opt-in deploy (preview per PR, prod on main), Dependabot
- **Docs:** C4 diagram, setup, runbooks (D1 escape hatch, backup/restore, Stripe isolation, passkey domain binding), ADR
## Known gaps
`.github/workflows/ci.yml` installs, lints, typechecks, tests and builds the template on every PR and push to `main`. As of 27 Sep 2026 these parts are scaffolded rather than wired end to end (tracked in `todo.md`):
- Turnstile: the widget renders on sign-in/sign-up, but no route calls `verifyTurnstile` in `lib/turnstile.ts`.
- Rate limiting: `lib/rate-limit.ts` exists, but no route calls it.
- Account deletion enqueues a purge job, but no queue consumer processes it.
- The root layout sets canonical `/`, so pages without their own `alternates.canonical` (pricing, blog index, auth pages) point search engines at the homepage.
- Billing: the pricing button reads `NEXT_PUBLIC_STRIPE_PRICE_*` while `.env.example` defines `STRIPE_PRICE_*`; the dashboard "Manage billing" form receives the portal URL as JSON instead of being redirected; the webhook records an event before applying it, so a failed apply is not retried.
## Intentionally not included
- Monorepo / Turborepo (flat app; promote only when needed)
- Multi-tenancy / orgs (single-user model; extension path in ADR)
- i18n / hreflang
- Storybook
- Status page
- Newsletter
- Custom ML/AI features
---
## Philosophy
Every dependency was picked to **reduce later regret, not to maximize current convenience**:
- Stripe *hosted* Checkout over embedded payment UI → card details are entered on Stripe's page, not yours, and CSP stays simple
- D1 *with a repo boundary* → cheap today, swappable when you outgrow D1's size limits
- Flat app *without* a monorepo → no scaffolding tax until you actually have multiple apps
- Better Auth *without* custom CSRF → one layer, not two that fight each other
- SSG for SEO pages *always* → crawlers see instant HTML, Google rewards you, cache misses never hit origin
---
## License
MIT, see `LICENSE`.
## Contributing
PRs welcome. Before submitting:
```bash
pnpm lint && pnpm typecheck && pnpm test
```
Keep the template opinionated: if a feature is "nice to have for some projects", put it in `docs/` as an extension, not in the main template.
Explore similar templates
Compare previews and descriptions within the same stack and category.

Next.js SaaS Starter
The official Next.js SaaS starter template, integrating email/password authentication, Stripe Checkout subscriptions, a dashboard with user/team CRUD, basic Owner/Member RBAC, and activity logging.
Next.js · SaaS

Next.js Commerce
Vercel's officially maintained Next.js App Router ecommerce starter with deep Shopify integration, offering a complete product catalog, cart, and checkout flow. Core logic is modular for swapping backends, and community provider versions exist for BigCommerce, Medusa, and Saleor.
Next.js · E-commerce

Next-js-Boilerplate
A developer-first Next.js 16 starter with Tailwind CSS 4 and TypeScript, supporting both App Router and Page Router.
Next.js · Starter

tailwind-nextjs-starter-blog
A Next.js and Tailwind CSS blog starter template using Contentlayer for Markdown/MDX, with built-in SEO, code highlighting, and tags. It targets personal technical blogs and migration from Jekyll/Hugo to React, and supports one-click Vercel deployment.
Next.js · Blog

next-forge
next-forge is a Turborepo-based Next.js template maintained by Vercel, positioned as a starting point for SaaS products.
Next.js · Starter

SaaS-Boilerplate
SaaS-Boilerplate is an open-source SaaS starter template built with Next.js App Router, React 19, TypeScript, Tailwind CSS, and Shadcn UI.
Next.js · SaaS
