Inicio rápido
Ejecuta la plantilla Sushi SaaS en local con pnpm, recorre rutas i18n, health checks y blogs MDX, y aprende dónde configurar autenticación, pagos y documentación.
Antes de empezar
Este inicio rápido asume conocimientos básicos para avanzar rápido:
- Conoces JavaScript (puedes leer/escribir JS moderno).
- Estás familiarizado con los elementos marcados como requisitos en esta guía (o al menos tienes una idea general de qué son).
Desarrollo local
Requisitos: Node 20+ y pnpm 9+.
git clone https://github.com/PansaLegrand/saas-sushi-template.git saas-sushi-template
cd saas-sushi-template
pnpm install
pnpm dev
Rutas útiles:
- Landing:
/en
,/zh
,/es
,/fr
,/ja
- Salud:
/api/health
(responde{ status: "ok" }
) - Docs (MDX):
/:locale/blogs/quick-start
Internacionalización (next-intl)
- Textos en
messages/*.json
- Locales en
src/i18n/locale.ts
(configurarlocales
,defaultLocale
,localePrefix
) - Middleware en
src/middleware.ts
(prefijo/:locale/...
) - Carga de mensajes en
src/i18n/request.ts
Añadir un idioma:
- Crear
messages/<locale>.json
- Añadir el código a
locales
ensrc/i18n/locale.ts
- Reiniciar el servidor
Docs con MDX (Fumadocs)
Los archivos viven en content/docs/<locale>/...
y se sirven en /:locale/blogs/<slugs>
.
- El script de desarrollo genera
.source/index.ts
automáticamente - El plugin de Fumadocs en
next.config.ts
analiza MDX y frontmatter
Crear una página:
mkdir -p content/docs/es
echo "---\ntitle: Mi página\n---\n\n# Hola" > content/docs/es/my-page.mdx
Visita /es/blogs/my-page
.
Variables de entorno
Antes de seguir con la configuración, crea un archivo .env
y copia la plantilla. Así centralizas secretos y URLs y la app puede configurar autenticación, almacenamiento y pagos.
- Crea
.env
en la raíz del proyecto (copiando desde la plantilla):
cp .env.example .env
# o abre .env y pega la plantilla de abajo
-
Completa los valores necesarios (consulta las notas a continuación). Puedes dejar vacíos los bloques opcionales y volver más tarde.
-
Reinicia el servidor de desarrollo tras los cambios para que Next.js y las herramientas carguen las variables.
pnpm dev
Plantilla completa (.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=
Para qué sirve cada sección (y cuándo completarla)
- App y URLs: define primero las URLs base para que funcionen enlaces y redirecciones. Los valores de UI son opcionales;
NEXT_PUBLIC_AUTH_ENABLED=false
desactiva la UI de auth. ManténSNOWFLAKE_WORKER_ID=1
en local. - Base de datos:
DATABASE_URL
es obligatorio para migraciones de Drizzle y las tablas de Better Auth. - Autenticación y social:
BETTER_AUTH_SECRET
(généralo conopenssl rand -base64 32
); Google OAuth es opcional. - Email (Resend):
RESEND_API_KEY
yEMAIL_FROM
para restablecer contraseñas y correos de bienvenida (usa un dominio verificado). - Pagos (Stripe):
STRIPE_PRIVATE_KEY
ySTRIPE_WEBHOOK_SECRET
para checkout + webhooks;NEXT_PUBLIC_PAY_*
para redirecciones; los IDs de precio son opcionales. - Almacenamiento (S3/R2/MinIO): configura
STORAGE_*
(o sinónimos S3_*); los flags de path‑style/ACL ajustan el comportamiento por proveedor;NEXT_PUBLIC_UPLOAD_MAX_MB
solo afecta a la UI. - Analytics y Ads: GA y AdSense (solo en producción).
- Flags de demo: alterna la demo de reservas.
Autenticación (Better Auth)
- Configuración del servidor en
src/lib/auth.ts
con el handler/api/auth/[...all]/route.ts
- Cliente y hooks en
src/lib/auth-client.ts
(/[locale]/login
,signup
,me
) - Define
BETTER_AUTH_SECRET
,BETTER_AUTH_URL
yNEXT_PUBLIC_AUTH_BASE_URL
Ejemplo .env
:
BETTER_AUTH_SECRET=$(openssl rand -base64 32)
BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_AUTH_BASE_URL=http://localhost:3000
Problemas comunes
/zh
aparece en inglés: asegúrate delocalePrefix = "always"
y reinicia- “Unknown module type” para MDX: verifica el plugin de Fumadocs y reinicia (log
[MDX] types generated
) - 404 en docs: coloca el archivo en
content/docs/<locale>/...
y usa el slug correcto
Acerca de Sushi SaaS — Qué es y por qué confiar
Un starter SaaS listo para producción, con licencia MIT, centrado en lanzar productos reales: i18n, Better Auth, Drizzle ORM, Stripe, MDX, admin con RBAC y una DX práctica. Pensado para clonar, extender y confiar.
Autenticación y Admin
Configura roles de administrador de solo lectura y lectura/escritura, protege las APIs de admin y entiende el flujo de autenticación del template.