以 Cloudflare 为运行平台:Workers 计算、D1 数据库、KV 缓存、R2 存储、Queues 队列与 Email Routing 邮件路由
saas_template — Next.js SaaS模板
作者 omar16100
面向 Cloudflare 的 SaaS 启动模板,基于 Next.js App Router,集成 D1、Better Auth 与 Stripe,提供营销页、认证、仪表盘与 SEO 基础设施。
看看你的网站会是什么样
浏览真实页面截图,比较内容排版与手机效果。点击下方缩略图切换,放大查看整页细节。
全部页面与视图 2
截图用于了解视觉与布局,交互效果请以实际项目为准。
值得关注的模板亮点
从已有项目资料中,快速了解它的特点。
Better Auth 提供邮箱密码、Google OAuth、魔法链接、Passkeys 与 2FA
Stripe 托管 Checkout 与 Customer Portal,webhook 使用 D1 做幂等记录
SEO 基础设施:动态 sitemap、robots、RSS、JSON-LD、IndexNow、llms.txt 与预览环境 noindex
安全默认值:按路由划分的 CSP、HSTS、X-Frame-Options DENY、Referrer-Policy 与 Permissions-Policy
仓库层 db/repo/* 隔离 D1,便于后续替换数据库而不改动领域代码
模板详细介绍
这是一个以 Cloudflare 为首选运行平台的 SaaS 启动模板,仓库采用 MIT 许可。技术栈以 Next.js App Router 为核心,通过 @opennextjs/cloudflare 部署到 Cloudflare Workers,数据层使用 D1 配合 Drizzle,认证使用 Better Auth,支付使用 Stripe 托管 Checkout 与 Customer Portal。模板按路由区分渲染方式:营销页与博客走 SSG/ISR 以服务 SEO,认证与仪表盘走 SSR,API 路由运行在 Workers 上。仓库为扁平 Next.js 应用,未使用 monorepo。
模板包含营销页、定价页、博客、法律页、登录注册与重置密码、已认证仪表盘等页面结构,并内置动态 sitemap、robots、RSS、JSON-LD、IndexNow 端点、llms.txt 以及同意门控的 GA4 与 PostHog 分析。安全方面提供按路由划分的 CSP、严格安全响应头、Stripe webhook 的 D1 幂等记录,以及账户删除与数据导出到 R2 的流程。
需要注意,README 明确列出若干尚未端到端接通的模块:Turnstile 组件已渲染但无路由调用验证函数,限流工具存在但未接入路由,账户删除仅入队而无消费者处理,根布局的 canonical 会影响未单独设置 canonical 的页面,计费部分存在价格变量命名不一致、门户跳转与 webhook 重试逻辑问题。模板有意不包含多租户、i18n、Storybook、状态页与 Newsletter。
适合你的项目吗?
适用场景
- 需要以 Cloudflare Workers 与 D1 为运行基础的 SaaS 项目起步
- 希望营销页与博客使用 SSG/ISR、认证与仪表盘使用 SSR 的 Next.js 应用
- 需要内置认证、订阅计费、邮件模板与 SEO 元数据的启动模板
- 希望在同一平台内使用计算、数据库、对象存储、队列与邮件路由的部署方式
适合人群
- 熟悉 Next.js App Router 与 Cloudflare 生态的开发者
- 单用户模型的 SaaS 产品原型或早期项目
- 接受模板为意见化结构、愿意按需替换组件的团队
- 需要 SEO 友好的营销站与受保护仪表盘并存的场景
上手与定制指南
结合仓库当前版本的说明,按下面的资料检查内容、配置与运行要求。
- 1
克隆后执行 pnpm install,复制 .env.example 为 .env.local,并至少生成 BETTER_AUTH_SECRET
- 2
使用 wrangler 创建 D1、KV、R2 与 Queues 资源,并将返回的 ID 填入 wrangler.toml 中的 REPLACE_ME 位置
- 3
按环境通过 wrangler secret put 配置 BETTER_AUTH_SECRET、STRIPE_SECRET_KEY、STRIPE_WEBHOOK_SECRET、RESEND_API_KEY 与 TURNSTILE_SECRET_KEY
- 4
NEXT_PUBLIC_* 变量不是密钥,需在构建环境设置,wrangler.toml 的 vars 仅覆盖运行时读取
- 5
部署前需完成域名路由、Resend 发信域验证、Stripe webhook 注册、Turnstile 站点创建与搜索平台验证等清单
授权与来源资料
使用条件与限制
免费使用 · 保留许可声明本次收录的是公开仓库中采用 MIT 许可的免费版本;免费范围为该版本代码,不代表外部服务或第三方素材免费。
使用时需要遵守
- 允许免费使用、修改、商用和分发该版本代码。
- 分发代码副本或实质性部分时,保留原作者版权声明和完整 MIT 许可声明。
- 代码按现状提供;担保与责任限制以原许可证及适用法律为准。
额外费用与待核对事项
- 模板需自行配置凭据才能完整运行:认证需 BETTER_AUTH_SECRET,Stripe、Resend、Turnstile 未配置时相关功能降级。
- 部署与创建资源需要 Cloudflare 账户并登录 wrangler,属于外部服务依赖。
- 部署需将域名接入 Cloudflare,本地开发不强制。
- 邮件发送依赖 Resend 服务,需验证发信域并配置 DKIM、SPF、DMARC。
- 计费功能依赖 Stripe,需按环境注册 webhook 并配置签名密钥。
- 人机验证依赖 Cloudflare Turnstile,需创建站点并配置密钥。
- 日志外发依赖 Axiom 服务,需创建数据集与令牌并配置 Logpush。
- 分析功能依赖 PostHog 与 GA4,需创建项目与媒体资源并配置 ID。
- 搜索平台验证需在 Google Search Console 与 Bing Webmaster Tools 注册站点。
- IndexNow 需自行生成密钥文件并放置于 public 目录。
- Turnstile 仅渲染组件,尚无路由调用验证函数,防护未端到端接通。
- 限流工具存在但未接入任何路由,实际未生效。
- 账户删除仅入队,无队列消费者处理,删除流程未完成。
- 根布局 canonical 指向首页,未单独设置 canonical 的页面会被搜索引擎指向首页。
- 计费存在缺陷:价格变量命名不一致、门户跳转未实现、webhook 失败不重试。
- 演示图片、字体、图标及其他第三方素材需按各自许可核对,不能仅凭代码的 MIT 许可判断。
- 域名、托管、数据库、支付或 AI 等外部服务可能另外收费。
查看原文依据
Permission is hereby granted, free of charge
许可证原文
The app boots with degraded features until you fill in credentials (auth needs the secret; Stripe/Resend/Turnstile each fail gracefully if unconfigured).
README 原文
a Cloudflare account · `wrangler login`
README 原文
Domain added to Cloudflare (can be a subdomain); only required for deploy, not local dev
README 原文
Verify your sending domain in **Resend** (DKIM + SPF + DMARC)
README 原文
Register a **Stripe** webhook endpoint per environment → paste signing secret
README 原文
Create **Turnstile** site → paste keys
README 原文
Create **Axiom** dataset + token → set up Cloudflare Logpush job → Axiom
README 原文
Create **PostHog** project + **GA4** property → paste IDs
README 原文
Add site to **Google Search Console** and **Bing Webmaster Tools** → paste verification tokens
README 原文
Generate `INDEXNOW_KEY` → serve at `/{key}.txt` under `public/`
README 原文
Turnstile: the widget renders on sign-in/sign-up, but no route calls `verifyTurnstile` in `lib/turnstile.ts`.
README 原文
Rate limiting: `lib/rate-limit.ts` exists, but no route calls it.
README 原文
Account deletion enqueues a purge job, but no queue consumer processes it.
README 原文
The root layout sets canonical `/`, so pages without their own `alternates.canonical` (pricing, blog index, auth pages) point search engines at the homepage.
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.
README 原文
DeepSeek 根据仓库许可证和 README 整理 ·2026-10-02。结果仅覆盖已读取资料,依赖、图片、字体及后续条款变更以各自原文为准。
项目核对记录与介绍依据
模板资料
- 来源平台
- GitHub
- 资料与维护参考分
- 资料较完整 · 88 / 100
- 技术栈
- Next.js · React
- 网站类型
- SaaS · 启动器
这个分数如何计算
本站按许可资料(最高30分)、演示链接(12分)、预览图(10分)、Star数(15分)、记录的维护时间(15分)、描述完整度(8分)、功能记录(5分)和分类资料(5分)计算。它不是代码、安全或性能测试,也不是 Google 评分;有链接不代表已实测可用。
这个项目需要核对什么
- 记录的仓库更新时间
- 2026-09-27
- 截图检查日期
- 2026-10-02
已收录演示链接。请用你的内容与屏幕尺寸检查;有演示不代表表单、认证或支付已经可用。
目录记录的主要技术栈是 Next.js。请通过依赖清单和锁文件核对实际版本与托管要求。这些说明基于收录资料,不代表代码审计或部署实测。
查看具体选型方法 →介绍的制作与核对状态
介绍由 AI 辅助整理目录与仓库资料。仓库作者不等于这篇介绍的审核者。
已完成服务器许可规则与 AI 来源核对;未经过人工编辑审核。
查看当时使用的来源资料
SHA-256: fe7ba73becb456ad7620e8b5acc049bc8d0d7ba0f44c92c5f6ad81c923aca0a8
2026-10-02T01:00:59.807Z
来源文件 / 仓库# 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.
看看同类模板
对比同技术栈与用途的模板,找到更适合的视觉与内容结构。

ai-website-cloner-template
Clone any website with one command using AI coding agents
Next.js · 启动器

Next.js SaaS Starter
Next.js SaaS Starter 是 Next.js 官方维护的 SaaS 起点模板,定位介于学习资源和可直接部署的脚手架之间。
Next.js · SaaS

Next.js Commerce
Next.js Commerce 是 Vercel 团队维护的电商启动模板,基于 Next.js App Router 构建,采用服务端渲染、React Server Components、Server Actions …
Next.js · 电商

Next-js-Boilerplate
Next-js-Boilerplate 是一个面向 Next.js 16、Tailwind CSS 4 和 TypeScript 的启动器,核心定位是开发者体验优先。
Next.js · 启动器

tailwind-nextjs-starter-blog
tailwind-nextjs-starter-blog 是一个面向个人技术博客的 Next.js 与 Tailwind CSS 启动模板,其 v2 版本基于 App Router 与 React Server Comp…
Next.js · 博客

next-forge
Production-grade Turborepo template for Next.js apps.
Next.js · 启动器
