Quick Start
Get the Sushi SaaS template running locally with pnpm, explore i18n routes, health checks, and MDX blogs, and learn where to configure auth, billing, and docs.
Before You Begin
This quick start assumes some baseline knowledge so you can move fast:
- You know JavaScript (can read modern JS).
- You’re familiar with the items listed as prerequisites in this guide (left panel, Prerequisites-Knowledge), or at least have a general idea of what they are.
Local Development
Prerequisites: 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
Open these URLs:
- Landing:
/en
,/zh
,/es
,/fr
,/ja
- Health:
/api/health
(returns{ status: "ok" }
) - Docs (MDX):
/:locale/blogs/quick-start
Internationalization (next-intl)
- Messages live in
messages/*.json
. - Locales are declared in
src/i18n/locale.ts
(setlocales
,defaultLocale
,localePrefix
). - Middleware is configured in
src/middleware.ts
and routes/:locale/...
. - Message loading is handled in
src/i18n/request.ts
.
To add a language:
- Add
messages/<locale>.json
- Add the code to
locales
insrc/i18n/locale.ts
- Restart dev
Docs via MDX (Fumadocs)
MDX docs live under content/docs/<locale>/...
. Pages are available at
/:locale/blogs/<slugs>
.
- The dev script generates
.source/index.ts
automatically. - MDX/Frontmatter is parsed by the Fumadocs Next plugin configured in
next.config.ts
.
Create a new page:
mkdir -p content/docs/en
echo "---\ntitle: My Page\n---\n\n# Hello" > content/docs/en/my-page.mdx
Visit /en/blogs/my-page
.
Environment Variables
Before you run through the rest of the setup, create a .env
file and copy the template values. This keeps secrets and URLs in one place and lets the app configure auth, storage, and payments.
- Create
.env
in the project root (copy from the template):
cp .env.example .env
# or open .env and paste the template below
-
Fill the values you need (see explanations just below). You can leave optional blocks empty and return later.
-
Restart the dev server after changes so Next.js and the tooling pick them up.
pnpm dev
Complete template (.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=
What Each Section Does (and When To Fill It)
- App Basics & URLs: Set your base URLs first so links and redirects work. UI defaults are optional;
NEXT_PUBLIC_AUTH_ENABLED
disables auth UI if set tofalse
. KeepSNOWFLAKE_WORKER_ID=1
for local. - Database:
DATABASE_URL
is required for Drizzle migrations and Better Auth tables. - Authentication & Social:
BETTER_AUTH_SECRET
(generate withopenssl rand -base64 32
); Google OAuth is optional. - Email (Resend):
RESEND_API_KEY
andEMAIL_FROM
for password resets and welcome emails (use a verified domain). - Payments (Stripe):
STRIPE_PRIVATE_KEY
andSTRIPE_WEBHOOK_SECRET
for checkout + webhooks;NEXT_PUBLIC_PAY_*
for redirects; Price IDs are optional. - Storage (S3/R2/MinIO): Set
STORAGE_*
(or S3_* synonyms) for uploads; path-style and ACL flags tune provider behavior;NEXT_PUBLIC_UPLOAD_MAX_MB
is a UI hint. - Analytics & Ads: GA and AdSense IDs (only used in production).
- Demo Feature Flags: Toggle the reservations demo.
Authentication (Better Auth)
- Server config lives at
src/lib/auth.ts
and is exposed via/api/auth/[...all]/route.ts
. - Client helpers live at
src/lib/auth-client.ts
and power/[locale]/login
,/[locale]/signup
, and/[locale]/me
. - Set
BETTER_AUTH_SECRET
,BETTER_AUTH_URL
, andNEXT_PUBLIC_AUTH_BASE_URL
in your env files.
Environment example:
BETTER_AUTH_SECRET=generate-with: openssl rand -base64 32
BETTER_AUTH_URL=http://localhost:3000
Troubleshooting
- If
/zh
renders English, ensurelocalePrefix = "always"
and restart. - If MDX says "Unknown module type", verify the Fumadocs plugin is enabled in
next.config.ts
and restart (it prints[MDX] types generated
). - If docs 404, ensure the file is inside
content/docs/<locale>/...
and slugs match the URL.
About Sushi SaaS — What It Is and Why It’s Reliable
A production‑ready, MIT‑licensed SaaS starter focused on shipping real products: i18n, Better Auth, Drizzle ORM, Stripe billing, MDX docs, RBAC admin, and practical DX. Built to be cloned, extended, and trusted.
Database Setup
Configure Postgres and Drizzle ORM — the backbone for auth, billing, storage, tasks, and app tables.