Next.js sobre Supabase de forma segura
Runbook DevOps para Next.js sobre Supabase: arquitectura, middleware, patrones de autenticación, rate limiting e integración con Claude Code.
Una vez que Supabase funciona de forma estable como plataforma backend en producción, se construye el stack de aplicación sobre ella.
En muchos proyectos modernos, Next.js asume el rol de capa de aplicación:
- Frontend Rendering
- Server Side Rendering
- Server Actions
- Route Handlers
- API Proxy
- Session Handling
Con ello, Next.js se convierte de facto en un gateway backend entre el navegador y Supabase.
El error más frecuente consiste en tratar esta capa como “frontend”, cuando en realidad contiene lógica del lado del servidor con privilegios elevados.
Este runbook describe cómo operar Next.js de forma segura sobre una plataforma Supabase self-hosted.
Cada sección contiene:
- Implementación
- Condición verificable
- Escenario de fallo
De un vistazo - Artículo 2 de 6 de la serie DevOps Runbook
- Next.js como contenedor separado aislado de Supabase
- Clave service_role solo en un archivo
- Middleware con getUser() en cada solicitud
- Verificación de ownership antes de mutaciones
- Rate limiting en endpoints de autenticación
Índice de la serie
Esta guía forma parte de nuestra serie de runbooks DevOps para app stacks self-hosted.
- Supabase Self-Hosting Runbook
- Next.js sobre Supabase de forma segura - este artículo
- Supabase Edge Functions de forma segura
- Trigger.dev Background Jobs en producción segura
- Claude Code como control de seguridad en el workflow DevOps
- Security Baseline para todo el stack
El artículo 1 describe la base de la plataforma. Este artículo describe la capa de aplicación sobre ella.
Visión general de la arquitectura
Browser (Client)
|
| HTTPS
|
Next.js App Layer
|
+-- @supabase/ssr
|
+-- service_role client
(solo contextos admin aislados)
|
Supabase Platform Layer
|
+-- Kong API Gateway
+-- GoTrue Auth
+-- PostgREST API
+-- Realtime
|
PostgreSQL Data Layer
|
+-- Row Level Security
Reglas básicas:
Browser -> solo se comunica con Next.js
Next.js -> se comunica con Supabase
Supabase -> controla el acceso mediante RLS
Cuando el navegador se comunica directamente con varios servicios backend, se generan fronteras de seguridad incontrolables.
Parte A - Decisiones de arquitectura
Estas decisiones se modifican rara vez y constituyen los cimientos.
A1 - Ejecutar Next.js como servicio independiente
Implementación
Next.js se ejecuta como contenedor propio.
services
nextjs-app
supabase-stack
postgres
Ejemplo docker-compose:
services:
nextjs-app:
build: ./app
ports:
- "3000:3000"
environment:
- NEXT_PUBLIC_SUPABASE_URL=http://kong:8000
- NEXT_PUBLIC_SUPABASE_ANON_KEY=${ANON_KEY}
- SUPABASE_SERVICE_ROLE_KEY=${SERVICE_ROLE_KEY}
networks:
- internal
Next.js no debe ejecutarse dentro del stack de Supabase.
Condición verificable
docker ps --format '{{.Names}}'
Resultado esperado:
nextjs-app
supabase-kong
supabase-postgres
supabase-auth
Escenario de fallo
Si Next.js se ejecuta en el mismo contenedor que Supabase:
- Se comparte el espacio de procesos
- Los secrets residen en el mismo entorno
- Un servidor Next.js comprometido tiene acceso directo a todos los servicios backend
A2 - El navegador solo se comunica con Next.js
Implementación
El navegador solo debe ver una URL pública.
https://app.example.com
No permitido:
https://app.example.com:8000
https://app.example.com:5432
https://app.example.com:9000
Ejemplo de firewall:
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 8000 -j DROP
iptables -A INPUT -p tcp --dport 5432 -j DROP
iptables -A INPUT -p tcp --dport 9000 -j DROP
Condición verificable
nmap -p 443,3000,5432,8000,9000 app.example.com
Resultado esperado:
443 open
todos los demás filtered
Escenario de fallo
Si Supabase Studio es accesible públicamente:
- Acceso completo a la base de datos
- Manipulación del esquema
- Acceso a los Storage Buckets
A3 - Configurar Security Headers
Next.js actúa como gateway y debe establecer HTTP Security Headers.
Implementación
next.config.js
const securityHeaders = [
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{ key: "Permissions-Policy", value: "camera=(), microphone=()" },
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
{
key: "Content-Security-Policy",
value: "default-src 'self'; img-src 'self' data: blob:; frame-ancestors 'none'"
}
]
Condición verificable
curl -I https://app.example.com
Headers esperados:
X-Frame-Options
Content-Security-Policy
Strict-Transport-Security
Escenario de fallo
Sin CSP:
- Los ataques XSS cargan scripts externos
- Los tokens pueden ser exfiltrados
Variables de entorno
| Variable | Visibilidad | Ubicación permitida | Riesgo de filtración |
|---|---|---|---|
| NEXT_PUBLIC_SUPABASE_URL | Cliente | .env, código cliente | Bajo (URL pública) |
| NEXT_PUBLIC_SUPABASE_ANON_KEY | Cliente | .env, código cliente | Medio (limitado por RLS) |
| SUPABASE_SERVICE_ROLE_KEY | Solo servidor | lib/supabase/admin.ts | Crítico (omite RLS) |
| DATABASE_URL | Solo servidor | .env (servidor) | Crítico (acceso directo a DB) |
| TRIGGER_API_KEY | Solo servidor | .env (servidor) | Alto (acceso a cola de tareas) |
Parte B - Verificaciones de implementación
Estas reglas se aplican a cada cambio de código.
B1 - Separar variables de entorno
Visibles para el cliente:
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
Solo servidor:
SUPABASE_SERVICE_ROLE_KEY
DATABASE_URL
TRIGGER_API_KEY
Condición verificable
grep -r "NEXT_PUBLIC_" .env*
Los secrets no deben aparecer ahí.
Build Leak Check
grep -r "SERVICE_ROLE" .next/
Resultado esperado:
ninguna coincidencia
Escenario de fallo
service_role en el bundle del cliente significa:
- Acceso completo a la base de datos
- RLS completamente inoperante
Quien conoce los fundamentos de la gestión de secrets comprende por qué esta separación es esencial.
B2 - Configurar correctamente el cliente Supabase SSR
Cliente del servidor:
import { createServerClient } from "@supabase/ssr"
El servidor utiliza la anon key, no service_role.
Cliente admin:
createClient(url, SERVICE_ROLE_KEY)
Solo para tareas administrativas.
Condición verificable
grep -rn "SERVICE_ROLE" app/
Resultado esperado: solo en
lib/supabase/admin.ts
Escenario de fallo
Cliente del servidor con service_role:
- RLS se omite completamente
- Todas las peticiones tienen privilegios de administrador
B3 - Middleware para Auth y Token Refresh
La middleware se ejecuta antes de cada petición.
middleware.ts
Implementación
const { data: { user } } = await supabase.auth.getUser()
No:
getSession()
Condición verificable
grep getUser middleware.ts
Escenario de fallo
Sin middleware:
- El refresco de tokens no funciona
- La sesión se interrumpe tras 1h
B4 - Patrón de mutación
Todas las mutaciones siguen el mismo flujo.
Auth
Input Validation
Ownership Check
Mutation
Logging
Ejemplo
const { user } = await supabase.auth.getUser()
if (!user) return
const post = await supabase
.from("posts")
.select("user_id")
.eq("id", postId)
if (post.user_id !== user.id)
throw new Error("forbidden")
Escenario de fallo
Sin Ownership Check:
Estadística: Según investigaciones de GitHub de 2024, más del 23% de las filtraciones de secrets en proyectos web involucran claves service_role o tokens backend equivalentes.
deletePost(id)
Un usuario puede eliminar datos ajenos.
B5 - Rate Limiting
Next.js no tiene rate limiting integrado.
Solución recomendada:
Upstash Redis
Ejemplo
10 requests / minute / IP
Condición verificable
for i in {1..20}
do curl -X POST /api/login
done
Resultado esperado:
HTTP 429
B6 - Logging sin secrets
Los logs no deben contener:
JWT Tokens
service_role keys
emails
passwords
Condición verificable
grep console.log app/
Parte C - Operación
C1 - Actualización de dependencias
Los releases de Next.js son frecuentes.
Verificar semanalmente:
npm audit
npm outdated
Recomendado:
Renovate / Dependabot
C2 - Integración de Claude Code
Arquitectura:
Git Push
|
Verificaciones deterministas
|
Informe de seguridad
|
Análisis de Claude
|
Decisión DevOps
Verificaciones deterministas
grep service_role
grep NEXT_PUBLIC
npm audit
Análisis de Claude
Claude verifica:
- Nuevas Server Actions
- Nuevas API Routes
- Patrones de Ownership
- Validación de entrada
- Deriva arquitectónica
Claude no ejecuta cambios en producción.
Lista de verificación de despliegue
Verificar antes de cada despliegue:
[ ] Next.js se ejecuta como servicio independiente
[ ] Puertos de Supabase cerrados externamente
[ ] Security Headers activos
[ ] Ningún service_role en el bundle del cliente
[ ] service_role solo en el cliente admin
[ ] middleware.ts presente
[ ] Server Actions verifican Auth
[ ] Ownership Checks implementados
[ ] Rate Limit de login activo
[ ] npm audit sin hallazgos críticos
Conclusión
Next.js en el stack moderno no es un frontend, sino una capa de servidor privilegiada.
La seguridad se construye mediante tres niveles:
Arquitectura
Verificaciones de implementación
Auditorías continuas
La combinación de verificaciones de seguridad en CI y análisis con Claude Code detecta tanto patrones conocidos como nuevos riesgos.
Quien aplica estos principios junto con una arquitectura Cert-Ready by Design construye seguridad verificable en lugar de auditorías posteriores.
Descarga de la lista de verificación
Prompt preparado para Claude Code. Suba el archivo a su servidor e inicie Claude Code en el directorio del proyecto de su aplicación Next.js. Claude Code verificará automáticamente todos los puntos de seguridad de este runbook e informará APROBADO, ADVERTENCIA o CRÍTICO.
claude -p "$(cat claude-check-artikel-2-nextjs-es.md)" --allowedTools Read,Grep,Glob,Bash
Descargar checklistÍndice de la serie
- Supabase Self-Hosting Runbook
- Next.js sobre Supabase de forma segura - este artículo
- Supabase Edge Functions de forma segura
- Trigger.dev Background Jobs en producción segura
- Claude Code como control de seguridad en el workflow DevOps
- Security Baseline para todo el stack
El siguiente artículo describe cómo utilizar Supabase Edge Functions de forma segura - sin construir una segunda arquitectura backend.

Bert Gogolin
Director General, Gosign
AI Governance Briefing
IA empresarial, regulación e infraestructura - una vez al mes, directamente de mi parte.