Neon es una base de datos Postgres sin servidor que se conecta a través de HTTP, se reduce a cero entre solicitudes y se ejecuta de forma nativa en Webflow Cloud. Sin TCP, sin grupos de conexiones que configurar y sin infraestructura adicional que gestionar.
Las opciones de almacenamiento integradas de Webflow Cloud cubren mucho terreno: clave-valor para la configuración, SQLite para datos estructurados y almacenamiento de objetos para archivos. Pero son SQLite. En el momento en que necesitas Postgres completo (un JOIN entre tres tablas, una columna JSON con indexación GIN, transacciones de lectura repetible, restricciones de clave foránea con ON DELETE CASCADE), necesitas implementaciones adicionales.
Neon es donde siempre termino volviendo. Es Postgres sin servidor diseñado específicamente para este entorno: la base de datos se escala automáticamente a cero cuando está inactiva, se ramifica como un repositorio de git y ofrece un controlador que se conecta a través de HTTP en lugar de TCP.
Esta guía construye una aplicación de lista de espera: un formulario de Webflow que envía correos electrónicos, un controlador de rutas de Webflow Cloud que los escribe en Neon y un punto final de lectura que devuelve el recuento actual. Este patrón se puede aplicar a cualquier modelo de datos que necesites.
¿Qué necesitas para conectar Neon Postgres a una aplicación de Webflow Cloud?
Necesitas tres cosas: un proyecto de Neon, una aplicación de Webflow Cloud y el @neondatabase/serverless controlador. No hay infraestructura adicional ni grupos de conexiones que configurar por separado. Neon gestiona la agrupación por su cuenta y tú solo haces referencia a la cadena de conexión agrupada desde tu aplicación.
Estos son los requisitos previos específicos antes de comenzar:
- Una cuenta de Neon: El plan gratuito incluye un proyecto con suficiente capacidad de cómputo para la mayoría de las aplicaciones iniciales.
- UnaWebflow Cloud App ya creada: usando Next.js (esta guía utiliza el App Router de Next.js, pero el mismo enfoque funciona con Astro); ejecuta
webflow auth login && webflow cloud initsi aún no lo has hecho. - Node.js 19 o superior: Es necesario para la versión GA del controlador sin servidor de Neon.
La configuración añade un paquete npm, una variable de entorno y dos controladores de ruta. Tiempo total: menos de 30 minutos si tu proyecto de Neon ya está creado.
5 pasos para crear una aplicación Neon sin servidor en Webflow Cloud
Los controladores de ruta residen en la aplicación en la nube. El formulario reside en el sitio de Webflow. Neon se sitúa fuera de ambos: es una base de datos Postgres totalmente gestionada a la que ambos entornos acceden mediante HTTP. Sin VPC, sin grupos de conexiones que cuidar.
Así es como encaja cada pieza.
1. Crea tu proyecto de Neon y aprovisiona el esquema
Cada proyecto de Neon viene con una base de datos predeterminada llamada neondb en la main rama. Ahí es donde va el esquema.
Inicia sesión en console.neon.tech, crea un nuevo proyecto y luego navega al editor SQL en la barra lateral izquierda.
Ejecuta esto para crear la tabla de la lista de espera:
CREATE TABLE waitlist (
id SERIAL PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX waitlist_joined_at_idx ON waitlist (joined_at DESC);
La UNIQUE restricción en email significa que los envíos duplicados devuelven un error de Postgres en lugar de crear filas duplicadas. Manejo eso con elegancia en el controlador de ruta en el paso 3. La restricción aplica la regla de negocio a nivel de base de datos, que es donde debe estar.
Obtención de tu cadena de conexión
A continuación, obtén tu cadena de conexión. Haz clic en Connect en el panel del proyecto para abrir la ventana modal de conexión. En el menú desplegable, cambia de Direct connection a Pooled connection. Esto añade -pooler al nombre de host.
Las conexiones agrupadas (pooled) son necesarias para entornos serverless, ya que Cloudflare Workers no puede mantener un socket TCP abierto entre solicitudes.
Copia la cadena; se ve así:
postgresql://alex:AbC123dEf@ep-cool-darkness-123456-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require
Punto de control: El editor SQL debería mostrar la tabla creada sin errores. Copia la cadena de conexión agrupada antes de cerrar la ventana modal. La necesitarás en el siguiente paso.
2. Instala el controlador serverless de Neon en tu Cloud App
El paquete @neondatabase/serverless es la única dependencia que necesitas. Funciona mediante HTTP y WebSockets en lugar de TCP, que es lo que lo hace compatible con Cloudflare Workers.
En la raíz de tu proyecto Cloud App:
npm install @neondatabase/serverless
Tras la instalación, crea un asistente para el cliente de base de datos en lib/db.ts. Centralizar la conexión aquí significa que cada Route Handler importa desde un único lugar en lugar de instanciar su propio cliente.
Así es como se ve ese asistente:
// lib/db.ts
import { neon } from '@neondatabase/serverless';
if (!process.env.DATABASE_URL) {
throw new Error('DATABASE_URL environment variable is not set');
}
export const sql = neon(process.env.DATABASE_URL);
La función neon() devuelve una función de consulta de plantilla literal etiquetada. Las consultas escritas como sql`SELECT * FROM waitlist WHERE id = ${id}` se parametrizan automáticamente. El controlador separa el SQL de los valores antes de enviar la solicitud, por lo que no hay riesgo de inyección SQL por la interpolación de variables de plantilla.
Punto de control: Ejecuta npx tsc --noEmit para confirmar que TypeScript compila sin errores. Si falla en la importación de @neondatabase/serverless , comprueba que tu tsconfig.json tenga como destino ES2020 o superior.
3. Añade la cadena de conexión y crea el Route Handler de escritura
La variable DATABASE_URL debe configurarse en dos lugares: .env.local para el desarrollo local y en el panel de entorno de Webflow Cloud para producción.
Añádela a .env.local:
DATABASE_URL=postgresql://alex:AbC123dEf@ep-cool-darkness-123456-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require
Antes de desplegar, añade el mismo valor a tu entorno de Webflow Cloud. Ve a la configuración de tu sitio en Webflow, abre Webflow Cloud, selecciona tu entorno y añade DATABASE_URL en Variables de entorno. El valor se oculta tras guardarlo y nunca se expone en el paquete desplegado.
Ahora crea el endpoint de escritura en app/api/waitlist/route.ts:
// app/api/waitlist/route.ts
export const runtime = 'edge';
import { sql } from '@/lib/db';
export async function POST(request: Request) {
const { email } = await request.json();
if (!email || typeof email !== 'string' || !email.includes('@')) {
return Response.json({ error: 'Valid email required' }, { status: 400 });
}
try {
await sql`
INSERT INTO waitlist (email)
VALUES (${email.toLowerCase().trim()})
`;
return Response.json({ success: true });
} catch (err: any) {
// Postgres unique violation error code
if (err.code === '23505') {
return Response.json({ error: 'Already on the waitlist' }, { status: 409 });
}
console.error('Waitlist insert error:', err);
return Response.json({ error: 'Something went wrong' }, { status: 500 });
}
}
La directiva export const runtime = 'edge' le indica a Next.js que ejecute este controlador en el entorno de ejecución de Cloudflare Workers. Sin ella, el controlador utiliza Node.js por defecto, lo que funciona localmente pero no en producción en Webflow Cloud.
La comprobación err.code === '23505' detecta específicamente las infracciones de restricciones únicas de Postgres. Cuando un correo electrónico duplicado choca con la restricción UNIQUE , Neon devuelve el código de error 23505 en lugar de un 500 genérico. Siempre manejo este caso explícitamente. Devolver un 409 Conflict permite al cliente mostrar un mensaje de "ya estás registrado" en lugar de un error genérico.
Punto de control: Prueba el endpoint de escritura localmente:
curl -X POST http://localhost:3000/app/api/waitlist \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com"}'
# Expected: {"success":true}
curl -X POST http://localhost:3000/app/api/waitlist \
-H "Content-Type: application/json" \
-d '{"email": "test@example.com"}'
# Expected: {"error":"Already on the waitlist"} with 409 status
Abre el editor SQL de Neon y ejecuta SELECT * FROM waitlist; para confirmar que la fila aparece.
4. Crea el manejador de la ruta de lectura
El endpoint de lectura devuelve el recuento total de registros y los diez correos electrónicos más recientes. Tener un endpoint independiente para las lecturas mantiene la lógica limpia y facilita la adición de caché o límites de tasa de forma individual.
Crea :app/api/waitlist/count/route.ts
// app/api/waitlist/count/route.ts
export const runtime = 'edge';
import { sql } from '@/lib/db';
export async function GET() {
const [countResult, recentResult] = await sql.transaction([
sql`SELECT COUNT(*)::int AS total FROM waitlist`,
sql`SELECT email, joined_at FROM waitlist ORDER BY joined_at DESC LIMIT 10`,
]);
return Response.json({
total: countResult[0].total,
recent: recentResult,
});
}
sql.transaction()envía ambas consultas en un solo viaje de ida y vuelta HTTP a Neon. Lo uso aquí no porque la lectura requiera garantías transaccionales, sino porque reduce a la mitad el número de solicitudes HTTP. Con Cloudflare Workers en el borde (edge), cada viaje de ida y vuelta a la base de datos añade latencia, por lo que agrupar las lecturas es importante a escala.
El cast en es un detalle de Postgres que vale la pena conocer. devuelve por defecto. El controlador de Neon serializa esto como una cadena en JSON (para evitar la pérdida de precisión de JavaScript en números enteros grandes). Convertirlo a significa que la respuesta JSON tiene en lugar de , que es lo que quieres al mostrar el recuento en una página de Webflow.::intCOUNT(*)COUNT(*)bigintint{ total: 47 }{ total: "47" }
Punto de control: Accede al endpoint de lectura localmente y confirma que tanto como devuelven los tipos esperados:totalrecent
curl http://localhost:3000/app/api/waitlist/count
# Expected: {"total":1,"recent":[{"email":"test@example.com","joined_at":"..."}]}
Una vez que el endpoint de lectura devuelve la estructura correcta, la capa de datos está completa. El paso 5 conecta la aplicación desplegada a un formulario de Webflow.
5. Despliega y conecta el formulario de Webflow
Despliega la aplicación en la nube:
webflow auth login
webflow cloud deploy
O haz un push a tu rama de GitHub conectada para activar un despliegue automático.
Una vez desplegada, añade el formulario y el JavaScript personalizado a tu sitio de Webflow.
En el Diseñador de Webflow, crea un formulario con:
- Un elemento de entrada de texto (Text Input) con ID y tipo configurado como
waitlist-emailEmail - Un botón de envío (Submit Button) con ID
waitlist-submit - Un bloque div (Div Block) con ID configurado en Display: None para el texto de respuesta
waitlist-message - Un bloque de texto (Text Block) opcional en algún lugar de la página con ID para mostrar el total de registros
waitlist-count
En la configuración de la etiqueta Before </body> de la página, añade:
<script>
const form = document.querySelector('[data-name="waitlist-form"]');
const emailInput = document.getElementById('waitlist-email');
const message = document.getElementById('waitlist-message');
const countEl = document.getElementById('waitlist-count');
// Load the current count on page load
async function loadCount() {
try {
const res = await fetch('/app/api/waitlist/count');
const data = await res.json();
if (countEl) countEl.textContent = `${data.total} people signed up`;
} catch (e) {
console.error('Count fetch failed:', e);
}
}
// Handle form submit
if (form) {
form.addEventListener('submit', async (event) => {
event.preventDefault();
const email = emailInput?.value?.trim();
if (!email) return;
try {
const res = await fetch('/app/api/waitlist', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
const data = await res.json();
message.style.display = 'block';
message.textContent = res.ok
? "You're on the list."
: data.error || 'Something went wrong.';
if (res.ok) loadCount();
} catch (e) {
message.style.display = 'block';
message.textContent = 'Something went wrong.';
}
});
}
loadCount();
</script>
event.preventDefault()evita que Webflow gestione el envío del formulario de forma nativa. Las llamadas van a y , que se resuelven en los manejadores de ruta de la aplicación en la nube porque Webflow Cloud monta la aplicación en en el mismo dominio.fetch/app/api/waitlist/app/api/waitlist/count/app/
Punto de control: Envía un correo electrónico real en la página publicada, abre el editor SQL de Neon y ejecuta . La fila debería aparecer en menos de un segundo tras el envío.SELECT * FROM waitlist ORDER BY joined_at DESC LIMIT 5;
¿Qué causa que las consultas de Neon fallen en Webflow Cloud?
La mayoría de los fallos de Neon y Webflow Cloud encajan en uno de estos cuatro patrones. Cada uno tiene una ruta de diagnóstico clara.
El controlador pg estándar falla al importar
Si ves un error como Error: Cannot find module 'net' o TypeError: dns.lookup is not a function, es que has instalado el paquete estándar pg (node-postgres) en lugar de @neondatabase/serverless.
Los controladores estándar de Postgres dependen de los módulos net y dns de Node.js para establecer conexiones TCP. Cloudflare Workers no expone esos módulos. Sustituye pg por @neondatabase/serverless y actualiza tus importaciones de import { Pool } from 'pg' a import { neon } from '@neondatabase/serverless'.
DATABASE_URL no disponible en el Route Handler
El Route Handler devuelve un error 500 con el mensaje "DATABASE_URL environment variable is not set". Esto significa que .env.local falta en el entorno local o que la variable no se añadió al panel de Webflow Cloud antes de realizar el despliegue.
Las variables de entorno añadidas al panel de Webflow Cloud solo surten efecto tras un nuevo despliegue. Añadirlas durante una sesión no reinicia el Worker. Añade la variable y vuelve a desplegar.
Cadena de conexión directa en lugar de agrupada (pooled)
Las consultas funcionan en local pero agotan el tiempo de espera en producción. El síntoma: las peticiones se quedan bloqueadas durante 10-30 segundos antes de fallar. La causa: estás usando la cadena de conexión directa (sin -pooler en el nombre de host) en lugar de la agrupada.
Las conexiones directas usan TCP, algo que los Workers no soportan. En la consola de Neon, ve a Project Dashboard → Connect, activa el interruptor de conexión Pooled, copia la cadena actualizada y vuelve a desplegar.
Las conexiones del pool no se cierran entre peticiones
Si usas Pool en lugar de neon() para transacciones interactivas, verás errores de agotamiento de conexiones bajo carga: Error: remaining connection slots are reserved. En Cloudflare Workers, las conexiones Pool no pueden sobrevivir a una única petición.
Crea y cierra el pool dentro del manejador, nunca en el ámbito del módulo:
// Correct: pool created and closed inside the handler
export async function POST(request: Request) {
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
try {
const result = await pool.query('INSERT INTO waitlist (email) VALUES ($1)', [email]);
return Response.json({ success: true });
} finally {
await pool.end(); // always close
}
}
// Wrong: pool at module scope leaks connections across requests
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
Para consultas sencillas sin transacciones interactivas, neon() sobre HTTP es la mejor opción y no tiene esta limitación.
Próximos pasos para tu integración de Neon y Webflow Cloud
Ya tienes Postgres serverless funcionando junto a tu sitio de Webflow, con un endpoint de escritura que gestiona los duplicados con elegancia y un endpoint de lectura que agrupa las consultas en un solo viaje de ida y vuelta HTTP.
La siguiente capa que suelo añadir es un ORM. Drizzle ORM se integra de forma nativa con Neon. El adaptador drizzle-orm/neon-http envuelve la función neon() y te ofrece consultas con seguridad de tipos sin salir de TypeScript. Las migraciones de esquema son archivos controlados por versiones en lugar de sesiones manuales en el editor SQL.
Explora Webflow + Neon para ver qué más puedes construir con Neon en Webflow Cloud, incluyendo tareas en segundo plano, autenticación y arquitecturas de datos multiinquilino.
Preguntas frecuentes
¿Por qué no puedo usar el controlador estándar de pg con Webflow Cloud?
Los controladores estándar de Postgres abren conexiones TCP usando el módulo net de Node.js. Cloudflare Workers, que impulsa Webflow Cloud, se ejecuta en un entorno aislado basado en V8 que no expone primitivas TCP. Cualquier controlador que use net.createConnection() fallará al importar. El controlador @neondatabase/serverless resuelve esto conectándose mediante HTTP (fetch) o WebSockets, ambos soportados de forma nativa por los Workers.
¿El nivel gratuito de Neon sirve para aplicaciones en producción?
El nivel gratuito de Neon incluye 100 proyectos, cada uno con 0,5 GB de almacenamiento y 100 horas de unidad de cómputo (CU) al mes. Para una lista de espera con poco tráfico o una herramienta interna, es más que suficiente. La base de datos se reduce a cero cuando está inactiva, por lo que una aplicación con poco uso apenas consume recursos. Para mayor tráfico o almacenamiento, los planes de pago de Neon se basan en el uso. Consulta la página de precios de Neon para ver los límites actuales.
¿Cuándo debería usar Pool en lugar de la función neon()?
Usa la neon() función HTTP para consultas únicas y transacciones por lotes no interactivas. Usa Pool (modo WebSocket) cuando necesites transacciones interactivas en las que varias consultas deban compartir una sesión y la entrada de la segunda consulta dependa del resultado de la primera. En ambos casos en Webflow Cloud, asegúrate de que cualquier Pool instancia se cree y se cierre dentro del mismo controlador de solicitudes. Las instancias de pool con alcance de módulo filtran conexiones y agotan los límites de conexión de Neon bajo carga.
¿Puedo usar Drizzle ORM o Prisma con Neon en Webflow Cloud?
Sí para Drizzle, con una advertencia para Prisma. Drizzle funciona perfectamente con el @neondatabase/serverless controlador a través de drizzle-orm/neon-http y se ejecuta bien en el entorno de ejecución edge. Prisma requiere @prisma/adapter-neon y el Pool modo WebSocket, que funciona en Workers pero requiere una gestión cuidadosa del ciclo de vida de la conexión. Uso Drizzle en producción en Webflow Cloud; Prisma es posible, pero añade carga de configuración. La documentación del controlador serverless de Neon muestra ejemplos de configuración para ambos.




