Next.jsReactSaaS启动器

saas_template — Next.js SaaS模板

作者 omar16100

面向 Cloudflare 的 SaaS 启动模板,基于 Next.js App Router,集成 D1、Better Auth 与 Stripe,提供营销页、认证、仪表盘与 SEO 基础设施。

看看你的网站会是什么样

浏览真实页面截图,比较内容排版与手机效果。点击下方缩略图切换,放大查看整页细节。

2 个预览视图
桌面端首页1 / 2
桌面端截图首屏预览

由服务器浏览器实际访问页面后截取。

全部页面与视图 2

截图用于了解视觉与布局,交互效果请以实际项目为准。

采集于 2026-10-01查看来源

值得关注的模板亮点

从已有项目资料中,快速了解它的特点。

01

以 Cloudflare 为运行平台:Workers 计算、D1 数据库、KV 缓存、R2 存储、Queues 队列与 Email Routing 邮件路由

02

Better Auth 提供邮箱密码、Google OAuth、魔法链接、Passkeys 与 2FA

03

Stripe 托管 Checkout 与 Customer Portal,webhook 使用 D1 做幂等记录

04

SEO 基础设施:动态 sitemap、robots、RSS、JSON-LD、IndexNow、llms.txt 与预览环境 noindex

05

安全默认值:按路由划分的 CSP、HSTS、X-Frame-Options DENY、Referrer-Policy 与 Permissions-Policy

06

仓库层 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。

Next.jsSaaS启动器

适合你的项目吗?

适用场景

  • 需要以 Cloudflare Workers 与 D1 为运行基础的 SaaS 项目起步
  • 希望营销页与博客使用 SSG/ISR、认证与仪表盘使用 SSR 的 Next.js 应用
  • 需要内置认证、订阅计费、邮件模板与 SEO 元数据的启动模板
  • 希望在同一平台内使用计算、数据库、对象存储、队列与邮件路由的部署方式

适合人群

  • 熟悉 Next.js App Router 与 Cloudflare 生态的开发者
  • 单用户模型的 SaaS 产品原型或早期项目
  • 接受模板为意见化结构、愿意按需替换组件的团队
  • 需要 SEO 友好的营销站与受保护仪表盘并存的场景

上手与定制指南

结合仓库当前版本的说明,按下面的资料检查内容、配置与运行要求。

  1. 1

    克隆后执行 pnpm install,复制 .env.example 为 .env.local,并至少生成 BETTER_AUTH_SECRET

  2. 2

    使用 wrangler 创建 D1、KV、R2 与 Queues 资源,并将返回的 ID 填入 wrangler.toml 中的 REPLACE_ME 位置

  3. 3

    按环境通过 wrangler secret put 配置 BETTER_AUTH_SECRET、STRIPE_SECRET_KEY、STRIPE_WEBHOOK_SECRET、RESEND_API_KEY 与 TURNSTILE_SECRET_KEY

  4. 4

    NEXT_PUBLIC_* 变量不是密钥,需在构建环境设置,wrangler.toml 的 vars 仅覆盖运行时读取

  5. 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 原文

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.

README 原文
https://github.com/omar16100/saas_template/blob/main/LICENSEhttps://github.com/omar16100/saas_template/blob/main/README.md

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

[![Website](https://img.shields.io/badge/website-omar16100.github.io-f38020)](https://omar16100.github.io/saas_template/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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.
反馈内容错误

看看同类模板

对比同技术栈与用途的模板,找到更适合的视觉与内容结构。