Ir al contenido
Infraestructura & Tecnología

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.

Mansoor Ahmed
Mansoor Ahmed
Head of Engineering 14 min de lectura

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.

  1. Supabase Self-Hosting Runbook
  2. Next.js sobre Supabase de forma segura - este artículo
  3. Supabase Edge Functions de forma segura
  4. Trigger.dev Background Jobs en producción segura
  5. Claude Code como control de seguridad en el workflow DevOps
  6. 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

VariableVisibilidadUbicación permitidaRiesgo de filtración
NEXT_PUBLIC_SUPABASE_URLCliente.env, código clienteBajo (URL pública)
NEXT_PUBLIC_SUPABASE_ANON_KEYCliente.env, código clienteMedio (limitado por RLS)
SUPABASE_SERVICE_ROLE_KEYSolo servidorlib/supabase/admin.tsCrítico (omite RLS)
DATABASE_URLSolo servidor.env (servidor)Crítico (acceso directo a DB)
TRIGGER_API_KEYSolo 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

  1. Supabase Self-Hosting Runbook
  2. Next.js sobre Supabase de forma segura - este artículo
  3. Supabase Edge Functions de forma segura
  4. Trigger.dev Background Jobs en producción segura
  5. Claude Code como control de seguridad en el workflow DevOps
  6. 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

Bert Gogolin

Director General, Gosign

AI Governance Briefing

IA empresarial, regulación e infraestructura - una vez al mes, directamente de mi parte.

Sin spam. Cancelable en cualquier momento. Política de privacidad

Next.js Supabase Security DevOps Self-Hosting
Compartir este artículo

Preguntas frecuentes

¿Por qué Next.js no debe ejecutarse en el mismo contenedor que Supabase?

Si Next.js se ejecuta en el mismo contenedor que Supabase, se comparte el espacio de procesos y los secrets residen en el mismo entorno. Un servidor Next.js comprometido tendría entonces acceso directo a todos los servicios backend.

¿Por qué no se debe usar la clave service_role en el código del cliente?

La clave service_role omite todas las políticas de Row Level Security. Si acaba en el bundle del cliente, cualquier usuario tiene acceso completo a la base de datos y RLS queda completamente inoperante.

¿Qué sucede si una Server Action no tiene verificación de autenticación?

Sin una verificación getUser() en una Server Action, cualquier usuario con una cookie de sesión válida puede invocar la acción, incluso para datos de otros usuarios. Dado que las Server Actions son accesibles vía HTTP POST, un simple curl con una cookie robada es suficiente.