Documentacion
Dopplelganger para Next.js
Guia completa para instalar, configurar, customizar y publicar el boilerplate. Esta vista resume la documentacion que recibe el comprador dentro del repositorio.
Empezar
Introduccion
Dopplelganger es una base SaaS Next.js pensada para reemplazar la pantalla publica por tu producto y conservar infraestructura lista.
Que trae
El repositorio incluye una app funcional con autenticacion, dashboard protegido, billing, settings, emails, i18n, base de datos, tests y documentacion MDX.
La idea no es que uses la UI tal cual para siempre. La UI inicial existe para validar que la estructura funciona y para darte lugares claros donde reemplazar pantallas por tu producto real.
- Landing reemplazable en app/[locale]/page.tsx.
- Auth y dashboard separados por route groups.
- Providers de pago aislados para Stripe y Lemon Squeezy.
- Docs ES/EN incluidas para configurar y modificar el proyecto.
Cuando usarlo
Usalo cuando queres construir un SaaS con Next.js sin repetir setup de auth, pagos, emails e internacionalizacion.
No incluye multi-tenancy, analytics ni storage en v1. Eso mantiene la base mas simple y mas facil de adaptar.
Empezar
Instalacion local
El flujo local instala dependencias, crea el archivo de entorno, levanta PostgreSQL y genera Prisma Client.
Requisitos
Necesitas Node 22, pnpm 9 o superior y una base PostgreSQL. El repo trae docker-compose para levantar PostgreSQL local sin configurar un servicio externo.
- Node.js >=22 <23
- pnpm >=9
- Docker o PostgreSQL propio
Comandos iniciales
Despues de comprar y acceder al repo, clona el proyecto, instala dependencias y crea tu .env desde el ejemplo.
git clone https://github.com/matills/doppelganger.git
cd doppelganger
pnpm install
cp .env.example .env
pnpm db:generate
pnpm run devPrimer chequeo
Abri la app, crea una cuenta y confirma que podes entrar al dashboard. Si todavia no configuraste pagos, billing no debe romper: simplemente mostrara que no hay providers disponibles.
- Crear cuenta en /es/register o /en/register.
- Entrar al dashboard protegido.
- Actualizar perfil desde settings.
- Correr pnpm run typecheck antes de empezar cambios grandes.
Codebase
Estructura del proyecto
La app usa App Router, route groups para auth/dashboard y carpetas de dominio para auth, payments, billing, email y settings.
Mapa rapido
Las rutas publicas viven bajo app/[locale]. Auth y dashboard estan separados para que los layouts no se mezclen.
| Ruta/carpeta | Responsabilidad |
|---|---|
| app/[locale]/page.tsx | Landing reemplazable |
| app/[locale]/(auth) | Login, registro, reset password |
| app/[locale]/(dashboard) | Dashboard, billing y settings protegidos |
| app/api/webhooks | Webhooks de Stripe y Lemon Squeezy |
| lib/auth | Server actions, providers y validacion de auth |
| lib/payments | Interfaz comun y providers de pago |
| lib/email | Templates y envio de emails |
| messages | Traducciones ES/EN |
| docs | Documentacion MDX incluida |
Patrones
La mayoria de paginas son Server Components. Las mutaciones importantes usan Server Actions. Las integraciones externas se activan por variables y degradan sin crashear cuando faltan.
- Server Components por defecto.
- Server Actions para auth, settings y checkout.
- Provider pattern para pagos.
- i18n con next-intl y rutas localizadas.
Configuracion
Variables de entorno
Las variables controlan que integraciones aparecen. El template evita mostrar providers que no esten configurados.
Core
Estas variables son la base para que la app arranque, genere URLs correctas y pueda conectarse a la base de datos.
| Variable | Uso | Requerida |
|---|---|---|
| NEXT_PUBLIC_APP_URL | URL publica para callbacks, emails y sitemap | Si |
| AUTH_SECRET | Firma de sesiones y tokens | Si |
| DATABASE_URL | Conexion principal Prisma | Si |
| DIRECT_URL | Conexion directa para migraciones | Si |
Integraciones opcionales
OAuth, pagos, emails y rate limiting pueden activarse por partes. Si no completas una familia de variables, esa feature se oculta o se salta en desarrollo.
| Feature | Variables |
|---|---|
| OAuth | GOOGLE_CLIENT_ID, GITHUB_CLIENT_ID, DISCORD_CLIENT_ID |
| Stripe | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, STRIPE_*_PRICE_IDS |
| Lemon Squeezy | LEMON_SQUEEZY_API_KEY, STORE_ID, WEBHOOK_SECRET, VARIANT_IDS |
| RESEND_API_KEY, EMAIL_FROM | |
| Rate limit | UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN |
Configuracion
Base de datos
Prisma modela usuarios, cuentas OAuth, sesiones, tokens, suscripciones y compras.
Modelos incluidos
El schema Prisma cubre los modelos necesarios para Auth.js y billing. Subscription y Purchase estan separados porque tienen ciclos de vida diferentes.
- User, Account, Session y VerificationToken para auth.
- Subscription para pagos recurrentes.
- Purchase para pagos unicos.
- Campos de provider para poder soportar Stripe y Lemon Squeezy.
Migraciones
En local podes usar migrate dev. Para demos rapidas tambien podes usar db push, pero para entregar un producto conviene versionar migraciones.
pnpm db:generate
pnpm db:migrate --name init
pnpm db:studioFeatures
Autenticacion
Auth.js v5 con Prisma Adapter, credenciales, OAuth opcional y recuperacion de password.
Flujos incluidos
El comprador recibe pantallas de login, registro, forgot password y reset password. Los providers OAuth solo se muestran si existen variables configuradas.
- Credentials con bcryptjs.
- OAuth Google, GitHub y Discord opcionales.
- Password reset con tokens en base de datos.
- Proteccion de dashboard, billing y settings desde proxy.
Archivos clave
La configuracion principal vive en auth.ts. Las validaciones y acciones estan separadas para que puedas cambiar UI sin mezclar reglas de negocio.
| Archivo | Uso |
|---|---|
| auth.ts | Config de Auth.js y providers |
| lib/auth/actions.ts | Server actions de login/register/reset |
| lib/auth/providers.ts | Deteccion de OAuth configurado |
| components/auth/auth-card.tsx | UI base de formularios |
Features
Pagos
Stripe y Lemon Squeezy implementan una interfaz comun para checkout, portal, webhooks y catalogo.
Como decide que mostrar
Billing lee los providers disponibles por variables. Si Stripe no esta configurado, no muestra productos Stripe. Lo mismo aplica para Lemon Squeezy.
- Suscripciones y pagos unicos se agrupan por seccion.
- El guard evita compras duplicadas si ya hay acceso activo.
- Los webhooks sincronizan status, renovacion y acceso.
Lemon Squeezy
Para usar Lemon Squeezy crea un store, producto, variantes y webhook. Luego completa API key, store ID, webhook secret y variant IDs.
LEMON_SQUEEZY_API_KEY=
LEMON_SQUEEZY_STORE_ID=
LEMON_SQUEEZY_WEBHOOK_SECRET=
LEMON_SQUEEZY_PAYMENT_VARIANT_IDS=345678Stripe
Para Stripe crea productos y price IDs, configura webhook secret y agrega los price IDs segun sean suscripciones o pagos unicos.
STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_SUBSCRIPTION_PRICE_IDS=price_abc123
STRIPE_PAYMENT_PRICE_IDS=price_def456Features
Emails
Templates con React Email y envio con Resend cuando RESEND_API_KEY esta configurada.
Comportamiento
En desarrollo o sin API key, el envio se salta de forma segura. Esto evita errores por no tener proveedor de email el primer dia.
- Welcome email como template base.
- Password reset con URL generada desde NEXT_PUBLIC_APP_URL.
- Templates en lib/email/templates.tsx.
Features
Internacionalizacion
La app incluye rutas localizadas, mensajes separados y selector de idioma.
Estructura
Los locales disponibles estan en i18n/config.ts y los mensajes en messages/en.json y messages/es.json.
- Locale por defecto: es.
- Rutas bajo /es y /en.
- Metadata localizada desde app/[locale]/layout.tsx.
Producto
Customizacion
Los primeros cambios deberian enfocarse en marca, landing, copy, modelos propios y pantallas de producto.
Que cambiar primero
El template esta pensado para que reemplaces lo publico y conserves lo funcional. No arranques refactorizando auth o billing si todavia no definiste el producto.
- Reemplaza app/[locale]/page.tsx por tu landing.
- Actualiza app/layout.tsx con metadata real.
- Cambia messages/en.json y messages/es.json.
- Agrega modelos propios en prisma/schema.prisma.
- Crea rutas protegidas dentro de app/[locale]/(dashboard).
Agregar una pagina protegida
Las rutas dentro del grupo dashboard heredan sidebar, topbar y proteccion de sesion.
app/[locale]/(dashboard)/projects/page.tsx
components/dashboard/sidebar.tsx
messages/es.json
messages/en.jsonProducto
Testing
Vitest cubre helpers, validaciones, providers, emails, payments y rutas criticas.
Comandos
Antes de publicar o tocar integraciones, corre typecheck, tests y build.
pnpm run typecheck
pnpm run test
pnpm run buildPatron de tests
Las rutas API y Server Actions se testean importando handlers y mockeando dependencias externas.
- vi.hoisted para mocks compartidos.
- Testing Library para componentes interactivos.
- jsdom configurado para tests de React.
Produccion
Deploy
El deploy requiere variables completas, base productiva, webhooks publicos y dominio final.
Checklist
La mayor fuente de errores en produccion son URLs publicas y webhook secrets mal configurados.
- Setear NEXT_PUBLIC_APP_URL con el dominio real.
- Cargar DATABASE_URL y DIRECT_URL del proveedor productivo.
- Registrar webhooks de Stripe o Lemon Squeezy contra /api/webhooks.
- Verificar EMAIL_FROM con dominio validado en Resend.
- Correr build en el mismo entorno donde vas a publicar.
Produccion
Troubleshooting
Errores comunes y donde mirar cuando algo no aparece o no sincroniza.
Providers no aparecen
Si OAuth o pagos no aparecen, casi siempre falta una variable de entorno o el servidor sigue usando variables viejas.
- Reinicia el dev server despues de cambiar .env.
- Confirma que los IDs no tengan espacios o comillas extra.
- Revisa logs del server para errores de provider.
Webhook no sincroniza
El webhook secret debe ser el secret del endpoint, no la API key del proveedor.
- Comprueba que la URL publica coincide exactamente.
- Selecciona los eventos documentados.
- Usa Stripe CLI o ngrok para pruebas locales.