ハンズオン
クイックスタート
pnpm でローカル起動し、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
(locales
,defaultLocale
,localePrefix
) - ミドルウェア:
src/middleware.ts
(/:locale/...
) - メッセージ読込:
src/i18n/request.ts
言語を追加するには:
messages/<locale>.json
を追加src/i18n/locale.ts
のlocales
に言語コードを追加- サーバーを再起動
MDX ドキュメント (Fumadocs)
content/docs/<locale>/...
に配置すると /:locale/blogs/<slugs>
で表示されます。
- 開発起動時に
.source/index.ts
を自動生成 next.config.ts
の Fumadocs プラグインで frontmatter を解析
新規ページ:
mkdir -p content/docs/ja
echo "---\ntitle: マイページ\n---\n\n# こんにちは" > content/docs/ja/my-page.mdx
/ja/blogs/my-page
を開く。
環境変数
以降の設定に進む前に、プロジェクト直下に .env
を作成してテンプレートからコピーしてください。認証・ストレージ・決済などの設定を一箇所で管理できます。
- ルートに
.env
を作成(テンプレートをコピー):
cp .env.example .env
# もしくは .env を開いて下のテンプレートを貼り付け
-
必要な値を入力します(下の説明を参照)。任意の項目は後からでも問題ありません。
-
変更後は開発サーバーを再起動して Next.js 等に反映させます。
pnpm dev
完全テンプレート(.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=
各セクションの役割(いつ設定するかの目安)
- App 基本と URL: まずベース URL を設定してリンクやリダイレクトを正しく。UI デフォルトは任意。
NEXT_PUBLIC_AUTH_ENABLED=false
で認証 UI を無効化。ローカルではSNOWFLAKE_WORKER_ID=1
のままで OK。 - Database:
DATABASE_URL
は Drizzle のマイグレーションと Better Auth のテーブルに必須。 - Authentication/Social:
BETTER_AUTH_SECRET
(openssl rand -base64 32
で生成)。Google OAuth は任意。 - Email(Resend): パスワードリセットやウェルカムメール用に
RESEND_API_KEY
とEMAIL_FROM
(検証済みドメイン推奨)。 - Payments(Stripe):
STRIPE_PRIVATE_KEY
とSTRIPE_WEBHOOK_SECRET
。NEXT_PUBLIC_PAY_*
はリダイレクト URL。価格 ID は任意。 - Storage(S3/R2/MinIO):
STORAGE_*
(または S3_* 同義)を設定。path-style/ACL はプロバイダに応じて調整。NEXT_PUBLIC_UPLOAD_MAX_MB
は UI のヒントのみ。 - Analytics/Ads: GA と AdSense(本番のみ表示)。
- Demo フラグ: 予約デモ機能のトグル。
認証 (Better Auth)
- サーバー設定は
src/lib/auth.ts
にあり、/api/auth/[...all]/route.ts
で公開されます。 - クライアントヘルパーは
src/lib/auth-client.ts
にあり、/[locale]/login
、/[locale]/signup
、/[locale]/me
を提供します。 - 環境変数
BETTER_AUTH_SECRET
、BETTER_AUTH_URL
、NEXT_PUBLIC_AUTH_BASE_URL
を設定してください。
環境例:
BETTER_AUTH_SECRET=$(openssl rand -base64 32)
BETTER_AUTH_URL=http://localhost:3000
トラブルシューティング
/zh
が英語のまま:localePrefix = "always"
を確認し再起動- MDX の “Unknown module type”: Fumadocs プラグインを有効にし再起動(ログ
[MDX] types generated
) - 404:
content/docs/<locale>/...
と URL のスラッグを確認