上手实践

快速开始

本地启动 Sushi SaaS 模板,查看 i18n 路由、健康检查和 MDX 博客,了解认证、支付与文档的配置入口。

开始之前

为便于快速上手,本指南默认你具备以下基础:

  • 了解 JavaScript(能阅读/编写现代 JS)。
  • 对文中标注为「前置要求」的内容已熟悉,或至少有基本概念。

本地开发

前置条件:Node 20+,pnpm 9+。

git clone https://github.com/PansaLegrand/saas-sushi-template.git saas-sushi-template
cd saas-sushi-template
pnpm install
pnpm dev

常用地址:

  • 首页(多语言):/en/zh/es/fr/ja
  • 健康检查:/api/health(返回 { status: "ok" }
  • 文档(MDX):/:locale/blogs/quick-start

国际化(next-intl)

  • 文案在 messages/*.json
  • 语言列表在 src/i18n/locale.ts(配置 localesdefaultLocalelocalePrefix
  • 中间件位于 src/middleware.ts,路由前缀为 /:locale/...
  • 文案加载在 src/i18n/request.ts

新增语言步骤:

1)新增 messages/<locale>.json 2)在 src/i18n/locale.tslocales 中加入该语言 3)重启开发服务


MDX 文档(Fumadocs)

文档位于 content/docs/<locale>/...,访问路径为 /:locale/blogs/<slugs>

  • 启动开发时会自动生成 .source/index.ts
  • MDX/Frontmatter 由 next.config.ts 中的 Fumadocs 插件解析

新建页面:

mkdir -p content/docs/zh
echo "---\ntitle: 我的页面\n---\n\n# 你好" > content/docs/zh/my-page.mdx

访问 /zh/blogs/my-page


环境变量

继续其它配置之前,请先创建 .env 并从模板复制。这样可以把密钥与各类 URL 放在同一处,让应用正确配置认证、存储与支付。

1)在项目根目录创建 .env(从模板复制):

cp .env.example .env
# 或打开 .env 粘贴下方模板

2)填写你需要的值(见下方说明)。可先把可选项留空,后续再补齐。

3)修改后请重启开发服务,让 Next.js 与相关工具加载新变量:

pnpm dev

完整模板(.env.example)

.env.example
# =============================================================================
# App Basics & URLs
# =============================================================================
# Public site base URL (used for canonical links, share URLs, Stripe redirects)
NEXT_PUBLIC_WEB_URL=http://localhost:3000
# Better Auth server base URL (usually same as site URL)
BETTER_AUTH_URL=http://localhost:3000
# Client-side Better Auth base (edge cases only; safe to leave as site URL)
NEXT_PUBLIC_AUTH_BASE_URL=http://localhost:3000

# App naming and UI defaults
NEXT_PUBLIC_APP_NAME=Sushi SaaS
NEXT_PUBLIC_PROJECT_NAME=sushi-saas-template
NEXT_PUBLIC_DEFAULT_THEME=system           # system | light | dark
NEXT_PUBLIC_LOCALE_DETECTION=false         # "true" to auto-detect browser locale
# Toggle auth entirely (default enabled unless set to "false")
NEXT_PUBLIC_AUTH_ENABLED=true

# Unique ID generator worker (Snowflake). Keep as 1 in single-instance dev.
SNOWFLAKE_WORKER_ID=1

# =============================================================================
# Database (PostgreSQL)
# =============================================================================
# Example: postgresql://user:password@localhost:5432/mydb
DATABASE_URL=

# =============================================================================
# Authentication (Better Auth) & Social Providers
# =============================================================================
# Generate with: openssl rand -base64 32
BETTER_AUTH_SECRET=

# Google OAuth (optional). Create credentials at
# https://console.cloud.google.com/apis/credentials
# Redirect URI (dev): http://localhost:3000/api/auth/callback/google
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

# =============================================================================
# Email (Resend)
# =============================================================================
# https://resend.com — required for password reset and onboarding emails
RESEND_API_KEY=
EMAIL_FROM="Your Name <founder@your-domain.com>" # Use a verified domain

# =============================================================================
# Payments (Stripe)
# =============================================================================
# Server secret key and webhook secret
STRIPE_PRIVATE_KEY=
STRIPE_WEBHOOK_SECRET=

# Public payment routes (can be relative or absolute)
NEXT_PUBLIC_PAY_SUCCESS_URL=/pricing
NEXT_PUBLIC_PAY_FAIL_URL=/pricing
NEXT_PUBLIC_PAY_CANCEL_URL=/pricing

# Optional: Stripe Price IDs (subscriptions)
# These are safe to expose; leave blank to fall back to inline price_data
NEXT_PUBLIC_STRIPE_PRICE_LAUNCH_MONTHLY=
NEXT_PUBLIC_STRIPE_PRICE_SCALE_MONTHLY=
NEXT_PUBLIC_STRIPE_PRICE_LAUNCH_YEARLY=
NEXT_PUBLIC_STRIPE_PRICE_SCALE_YEARLY=
# If using CNY prices, set the variants below
NEXT_PUBLIC_STRIPE_PRICE_LAUNCH_MONTHLY_CNY=
NEXT_PUBLIC_STRIPE_PRICE_SCALE_MONTHLY_CNY=
NEXT_PUBLIC_STRIPE_PRICE_LAUNCH_YEARLY_CNY=
NEXT_PUBLIC_STRIPE_PRICE_SCALE_YEARLY_CNY=

# =============================================================================
# Storage (S3-compatible: AWS S3, Cloudflare R2, MinIO)
# =============================================================================
# Provider selector: s3 | r2 | minio
STORAGE_PROVIDER=s3
# Leave endpoint empty for AWS S3; for R2 use https://<accountid>.r2.cloudflarestorage.com
STORAGE_ENDPOINT=
STORAGE_REGION=auto
STORAGE_ACCESS_KEY=
STORAGE_SECRET_KEY=
STORAGE_BUCKET=
# Path-style addressing (recommended true for R2/MinIO). Auto-enabled when endpoint is set.
S3_FORCE_PATH_STYLE=true
# Include ACL only if your bucket requires it (most R2/MinIO do not)
S3_USE_ACL=false
# Max single-file upload size (MB)
STORAGE_MAX_UPLOAD_MB=25
# Client-only hint to display max size in the uploader
NEXT_PUBLIC_UPLOAD_MAX_MB=25
# Alternative AWS-style names (optional synonyms). Prefer STORAGE_* above.
S3_ENDPOINT=
S3_REGION=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_BUCKET=

# =============================================================================
# Analytics & Ads (optional)
# =============================================================================
# Google Analytics (G-XXXXXXX) — renders only in production
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=
# Google AdSense account code (e.g., ca-pub-XXXXXXXXXXXXXXXX)
NEXT_PUBLIC_GOOGLE_ADCODE=

# =============================================================================
# Demo Feature Flags
# =============================================================================
# Reservations demo (enabled by default)
NEXT_PUBLIC_FEATURE_RESERVATIONS_ENABLED=true
NEXT_PUBLIC_RESERVATIONS_AUTO_SEED_DEMO=true
# =============================================================================
# Logging
# =============================================================================
# Log level for server and edge logs: debug | info | warn | error
LOG_LEVEL=info

# =============================================================================
# Notifications (optional)
# =============================================================================
# Slack Incoming Webhook URL for ops alerts/notifications
SLACK_WEBHOOK_URL=

每个区块的作用(以及何时填写)

  • 基础与 URL:先设置基础 URL,确保链接与重定向正确;UI 默认项可选;NEXT_PUBLIC_AUTH_ENABLED=false 可关闭认证 UI;本地开发保持 SNOWFLAKE_WORKER_ID=1
  • 数据库:DATABASE_URL 是 Drizzle 迁移与 Better Auth 表所必需。
  • 认证与社交登录:BETTER_AUTH_SECRET(用 openssl rand -base64 32 生成);Google OAuth 可选。
  • 邮件(Resend):RESEND_API_KEYEMAIL_FROM 用于重置密码与欢迎邮件(需使用已验证域名)。
  • 支付(Stripe):STRIPE_PRIVATE_KEYSTRIPE_WEBHOOK_SECRET 用于结账与 webhook;NEXT_PUBLIC_PAY_* 是重定向地址;价格 ID 可选。
  • 存储(S3/R2/MinIO):配置 STORAGE_*(或 S3_* 同义);S3_FORCE_PATH_STYLE/S3_USE_ACL 用于不同供应商;NEXT_PUBLIC_UPLOAD_MAX_MB 仅影响前端提示。
  • 分析与广告:GA 与 AdSense(仅在生产环境渲染)。
  • Demo 开关:开关预订(reservations)演示功能。

认证(Better Auth)

  • 服务端配置:src/lib/auth.ts,处理器 /api/auth/[...all]/route.ts
  • 客户端与 hooks:src/lib/auth-client.ts(登录、注册、个人页)
  • 环境变量需设置 BETTER_AUTH_SECRETBETTER_AUTH_URLNEXT_PUBLIC_AUTH_BASE_URL

示例环境变量:

BETTER_AUTH_SECRET=使用 openssl 生成:openssl rand -base64 32
BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_AUTH_BASE_URL=http://localhost:3000

排错

  • /zh 仍是英文:检查 localePrefix = "always" 并重启
  • MDX 报 “Unknown module type”:确认已启用 Fumadocs 插件并重启(日志含 [MDX] types generated
  • 文档 404:确认文件在 content/docs/<locale>/... 且路径与访问的 slugs 一致