Cómo crear una aplicación con IA en Webflow Cloud usando OpenAI y Supabase

Cómo crear una aplicación con IA en Webflow Cloud usando OpenAI y Supabase

Aprende a crear una aplicación de chat con IA y streaming en Webflow Cloud usando OpenAI y Supabase.

Cómo crear una aplicación con IA en Webflow Cloud usando OpenAI y Supabase

Ismail Ajagbe
Redactor técnico
Ver perfil del autor
Ismail Ajagbe
Redactor técnico
Ver perfil del autor
Índice de contenidos

Unifica toda tu infraestructura de chat con IA dentro de Webflow Cloud para eliminar la complejidad de gestionar servicios proxy dispersos. Este enfoque consolida tus manejadores de rutas, la lógica de streaming y el historial de conversaciones en un único despliegue de alto rendimiento.

Antes de Webflow Cloud, añadir una función de IA real a un sitio de Webflow significaba hacer un desvío a través de un servicio independiente: una función de Vercel, un endpoint serverless de Netlify o un servidor Express completo alojado en otro lugar. La configuración funcionaba (lo he implementado varias veces), pero siempre daba la sensación de ser un apaño con cinta adhesiva.

Con Webflow Cloud, puedes tener una aplicación Next.js, incluyendo el manejador de rutas de OpenAI, las consultas del historial de conversaciones y la lógica de respuesta en streaming, ejecutándose dentro de la propia infraestructura de Webflow. Un solo despliegue, un panel de variables de entorno y un único lugar donde mirar cuando algo sale mal.

Este artículo explora cómo crear una función de chat con IA y streaming en Webflow Cloud: cómo inicializar correctamente el cliente de OpenAI para el entorno de ejecución de Cloudflare Workers, cómo transmitir tokens al navegador usando los Route Handlers de Next.js y cómo persistir el historial de conversaciones en Supabase para que tu IA tenga contexto entre sesiones.

¿Qué necesitas para crear una aplicación con IA en Webflow Cloud?

Necesitas un espacio de trabajo de Webflow con acceso a Cloud, la CLI de Webflow, Node.js y npm, una cuenta de OpenAI con una clave API financiada y un proyecto de Supabase para almacenar las conversaciones. El componente de historial de conversaciones es opcional.

Si estás creando una función de IA sin estado y de una sola interacción en lugar de un chat persistente, puedes saltarte Supabase y seguir solo los pasos 1 a 3.

Esto es lo que necesitas antes de escribir cualquier código de integración.

Un espacio de trabajo de Webflow con acceso a Cloud

Webflow Cloud está disponible en planes de espacio de trabajo de pago. Confirma que tienes acceso a la sección Cloud en tu panel de control de Webflow antes de empezar. Si no ves Cloud en la barra lateral izquierda, revisa el nivel de tu plan.

Node.js 22+ y npm

Webflow Cloud requiere Node.js 22 o superior, y npm es actualmente el único gestor de paquetes compatible. Confirma que ambos están disponibles con node -v y npm -v. Ten en cuenta que la CLI de Webflow eleva ligeramente el requisito mínimo, así que asegúrate de cumplir con la versión indicada en la lista de requisitos previos de Webflow Cloud en lugar de usar la que tengas en tu máquina.

La CLI de Webflow

La CLI gestiona la autenticación y el despliegue. Instálala de forma global:

npm install -g @webflow/webflow-cli

Confirma la instalación con webflow --version antes de inicializar el proyecto.

Una cuenta de OpenAI con una clave API

Necesitarás una cuenta de OpenAI con la facturación configurada y una clave API generada desde el panel de control de la plataforma de OpenAI. La clave API es secreta; nunca la pongas en el código del lado del cliente ni la subas al control de versiones.

La misma disciplina se aplica a cualquier llamada a OpenAI desde Webflow Cloud, algo que detallamos en nuestra guía sobre cómo usar un proxy para la generación de imágenes.

La añadirás en la sección Configuración → Variables de tu proyecto en Webflow Cloud, donde estará disponible para tus manejadores de rutas del lado del servidor en el momento de la solicitud.

Esta guía utiliza gpt-5.6-luna, el nivel de coste optimizado de la generación actual de OpenAI. Es compatible con Chat Completions y streaming, que es todo lo que necesita esta compilación, y a 0,20 $ por millón de tokens de entrada, es lo suficientemente barato para un volumen conversacional.

Cambia a gpt-5.6-terra para obtener un mejor equilibrio entre inteligencia y coste, o a gpt-5.6-sol (con el alias gpt-5.6) como modelo insignia, simplemente cambiando la cadena del modelo. Los tres aceptan el mismo formato de llamada, así que nada más en esta guía cambia.

Un ajuste que merece la pena conocer: estos modelos exponen un parámetro reasoning_effort que va desde none hasta max, con un valor predeterminado de medium. Para una interfaz de chat donde la latencia del primer token es lo que define toda la experiencia, un esfuerzo menor suele ser la mejor opción.

Un proyecto de Supabase para el historial de conversaciones

Crea un proyecto en supabase.com para almacenar el historial de conversaciones. Si también quieres usuarios registrados en lugar de sesiones anónimas, nuestra guía de autenticación de Supabase cubre esa configuración en Webflow Cloud.

Necesitarás la URL del proyecto y la clave pública (sb_publishable_...). Si solo quieres respuestas de IA sin estado y no necesitas memoria de conversación, este requisito previo es opcional. Los pasos del 1 al 3 cubren la implementación completa de streaming sin Supabase.

Una vez que todo esté listo, aquí tienes los cinco pasos para tener una aplicación con IA funcionando en Webflow Cloud.

5 pasos para crear una aplicación con IA en Webflow Cloud usando OpenAI y Supabase

Estos cinco pasos cubren la estructura del proyecto, la configuración del cliente de OpenAI, la implementación de una ruta de API de streaming, la persistencia del historial de conversaciones y el despliegue. Si ya has completado la guía full-stack de Webflow Cloud, los pasos 1 y 2 te resultarán familiares, ya que la configuración de Webflow Cloud es coherente en todas las integraciones.

La guía de inicio rápido de Webflow Cloud cubre la inicialización del proyecto con más detalle si eres nuevo en la plataforma. Para aplicaciones de IA, los pasos clave son el 3 y el 4.

Si ya has configurado un proyecto de Webflow Cloud, salta al paso 2.

1. Inicializa tu proyecto de Webflow Cloud

La estructura de un proyecto de Webflow Cloud es la misma independientemente de las integraciones que utilices.

Comienza con la autenticación CLI y la inicialización del proyecto:

webflow auth login
webflow cloud init

webflow auth login autentica tu sesión de CLI con tu espacio de trabajo de Webflow a través del navegador. webflow cloud init luego estructura el proyecto y vincula tu directorio local a una aplicación de Webflow Cloud.

Una nota sobre qué comandos usar, ya que la CLI está en plena migración. Webflow ahora trata el espacio de nombres apps como canónico (webflow apps init, webflow apps deploy) y documenta cloud init y cloud deploy como alias obsoletos.

Pero apps actualmente solo se distribuye en el canal de pre-lanzamiento @next de la CLI y no existe en la versión estable, por lo que los comandos cloud son los que funcionan en una instalación predeterminada hoy en día. Usa cloud ahora, y espera a cambiar a apps una vez que llegue a la versión estable.

Después de la inicialización, es posible que veas los archivos a continuación. Considéralos como algo para entender, no como algo que debas crear: Webflow Cloud lee tu package.json, detecta el framework y genera la configuración de despliegue por sí mismo, por lo que la mayoría de los proyectos no necesitan que nada de esto se confirme en el repositorio.

El archivo webflow.json

El archivo webflow.json es el selector de framework y es opcional. La detección del framework es automática; añade este archivo solo si quieres fijar la elección explícitamente en lugar de dejar que Webflow la infiera.

Para un proyecto de Next.js, contiene:

{
  "cloud": {
    "framework": "nextjs"
  }
}

Esto fija la canalización de compilación al adaptador OpenNext de Cloudflare. Su ausencia no hace que el despliegue falle, al contrario de lo que puedas leer en otros sitios: sin él, Webflow Cloud detecta el framework desde tu package.json y continúa.

El archivowrangler.jsonc

El wrangler.jsonc archivo es la configuración de Cloudflare Workers. Verás nodejs_compat en él, lo que habilita la compatibilidad con la API de Node.js en el entorno de ejecución de Workers, incluyendo node:crypto, node:buffer y node:stream.

Debes saber que Webflow Cloud aplica la compatibilidad con Node.js automáticamente al realizar el despliegue, así que no es una opción que debas recordar activar:

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "nextjs",
  "main": ".open-next/worker.js",
  "compatibility_date": "2025-04-15",
  "compatibility_flags": ["nodejs_compat"],
  "assets": {
    "binding": "ASSETS",
    "directory": ".open-next/assets"
  },
  "observability": { "enabled": true }
}

Este archivo no es solo para uso local, un detalle que vale la pena aclarar si has leído lo contrario. Wrangler lo lee para npm run preview. Aun así, Webflow Cloud también lee la configuración confirmada durante el despliegue para aprovisionar los enlaces de almacenamiento e inyectar los IDs reales.

Si falta un campo obligatorio, registra el error, continúa sin tus enlaces y el despliegue se completa con éxito, por lo que la aplicación se publica con el almacenamiento desconectado silenciosamente.

El archivoopen-next.config.ts

El open-next.config.ts archivo configura el adaptador de OpenNext y es mínimo para la mayoría de los proyectos:

import { defineCloudflareConfig } from "@opennextjs/cloudflare";
export default defineCloudflareConfig({});

La mayoría de los proyectos no necesitan modificar este archivo más allá de la exportación predeterminada. Se vuelve relevante si añades funciones avanzadas de OpenNext como ISR o adaptadores de caché personalizados, pero para esta arquitectura, se mantiene tal cual.

El archivocloudflare-env.d.ts

El cloudflare-env.d.ts archivo proporciona tipos de TypeScript para los enlaces de Cloudflare:

interface CloudflareEnv {}

Un comportamiento de las variables de entorno que vale la pena entender es dónde están disponibles los valores. Webflow Cloud hace que las variables de entorno estén disponibles para el proceso de compilación de tu aplicación y para la aplicación desplegada en tiempo de ejecución, con los valores secretos ocultos en los registros de compilación.

Cada entorno admite hasta 110 de ellas. Configura todas las variables (incluyendo OPENAI_API_KEY) en la sección Configuración → Variables de tu proyecto en el panel de control de Webflow.

Cualquier cosa en .env.local se aplica solo al desarrollo local y no se lee durante el despliegue en producción.

Configuración del entorno de vista previa local

Instala el adaptador de OpenNext y añade un script de vista previa para que puedas probarlo con el entorno de ejecución real de Workers antes de desplegar:

npm install @opennextjs/cloudflare

Luego añade esto a tus package.json scripts:

{
  "scripts": {
    "preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview"
  }
}

Ejecuta npm run preview antes de cada despliegue. La simulación de Wrangler que inicia es lo suficientemente cercana al entorno de ejecución de Workers en producción como para que, si el streaming funciona localmente en preview, funcionará en producción.

Los problemas de streaming que aparecen en dev pero no en preview (o viceversa) casi siempre se deben a diferencias en el entorno o en el tiempo de ejecución; preview te muestra la realidad.

2. Instala el SDK de OpenAI y comprende cómo funciona en el entorno de ejecución de Workers

El SDK de OpenAI tiene una integración impecable en Cloudflare Workers: está diseñado para entornos multiplataforma y utiliza fetch internamente, por lo que no hay nada que configurar.

Esto solía ser un punto de diferencia con Stripe, cuyo SDK de Node utilizaba por defecto un transporte node:https y necesitaba una anulación explícita de httpClient: Stripe.createFetchHttpClient() en Workers.

Eso ya no es así; el stripe-node actual incluye compilaciones para workers seleccionadas automáticamente por la condición de exportación worker , y estas ya utilizan el cliente fetch por defecto. Si mantienes esa anulación en una integración antigua, ahora es redundante en lugar de necesaria.

Esto significa que lo inicializas, lo llamas y recorres sus flujos exactamente como lo harías en cualquier entorno de Node.js.

Instalar el paquete y crear una fábrica de clientes compartida

Instala el SDK de OpenAI:

npm install openai

Crea una fábrica de clientes compartida en lib/openai.ts. El patrón de función de fábrica garantiza que la clave de API se lea en el momento de la solicitud y no durante la carga del módulo.

Webflow Cloud inyecta variables de entorno por solicitud en el tiempo de ejecución de Workers, por lo que las lecturas de process.env en el nivel superior pueden devolver undefined si ocurren durante la inicialización del módulo:

// lib/openai.ts
import OpenAI from "openai";

export function getOpenAIClient() {
  const apiKey = process.env.OPENAI_API_KEY;

  if (!apiKey) {
    throw new Error("Missing OPENAI_API_KEY environment variable");
  }

  return new OpenAI({ apiKey });
}

Sin httpClient anulaciones, configuraciones de fetch-transport ni casos especiales para el tiempo de ejecución en el borde (edge runtime). El SDK de OpenAI funciona en Cloudflare Workers exactamente como esperarías en Node.js. El patrón de streaming for-await funciona, la opción stream: true funciona y la ruta de importación estándar, import OpenAI from "openai", funciona.

Lo que significa que: si un tutorial te dice que cambies el cliente HTTP para un despliegue en Workers, verifica si ese consejo es anterior a que el SDK lanzara su propia compilación para workers. Para OpenAI, nunca fue necesario; para Stripe, ya no lo es.

Configuración de la variable de entorno

Añade una variable en la sección Configuración → Variables de tu proyecto de Webflow Cloud:

OPENAI_API_KEY=sk-...

Tu clave de API de OpenAI comienza con sk-. Esta es una variable exclusiva del lado del servidor; nunca debe aparecer en el código del lado del cliente ni en ninguna importación que se ejecute en el navegador. Dado que Webflow Cloud solo hace que las variables estén disponibles en los manejadores de rutas del lado del servidor (y no estás declarando ningún prefijo NEXT_PUBLIC_ ), no se filtrará al paquete del cliente.

3. Crea una ruta de API de chat con streaming

El streaming es el requisito técnico fundamental para una experiencia de chat con IA realista. Sin streaming, el navegador espera a recibir la respuesta completa antes de mostrar nada. En respuestas largas, esto significa 5 a 10 segundos de silencio, lo que arruina la sensación de conversación. Con el streaming, los tokens aparecen en tiempo real a medida que el modelo los genera.

La implementación del streaming consta de dos partes: la ruta del servidor que transmite los tokens al navegador y el componente del cliente que lee el flujo y renderiza los tokens a medida que llegan.

Creación de la ruta del servidor

Este manejador de rutas llama a OpenAI con stream: true, y luego canaliza el flujo de tokens a través de un TransformStream hacia la respuesta del navegador.

Toda la ruta se ejecuta en el tiempo de ejecución de Workers en Webflow Cloud:

// app/api/chat/route.ts
import { getOpenAIClient } from "@/lib/openai";
import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest) {
  const { messages } = await request.json();

  if (!messages || !Array.isArray(messages)) {
    return NextResponse.json(
      { error: "messages array is required" },
      { status: 400 }
    );
  }

  const openai = getOpenAIClient();

  const { readable, writable } = new TransformStream();
  const writer = writable.getWriter();
  const encoder = new TextEncoder();

  // Stream in the background, piping tokens to the TransformStream
  const streamPromise = (async () => {
    const stream = await openai.chat.completions.create({
      model: "gpt-5.6-luna",
      messages,
      stream: true,
    });

    try {
      for await (const part of stream) {
        const token = part.choices[0]?.delta?.content || "";
        if (token) {
          // Await the write. Dropping this promise discards
          // backpressure and, when the client disconnects, leaves a
          // rejected promise nothing is listening for.
          await writer.write(encoder.encode(token));
        }
      }
    } finally {
      // close() in finally, so a thrown error still ends the stream.
      // Never abort() after close() — that rejects too.
      await writer.close().catch(() => {});
    }
  })();

  // Don't await — return the ReadableStream immediately so tokens flow to the browser
  streamPromise.catch((err) => {
    console.error("OpenAI stream error:", err);
  });

  return new Response(readable, {
    headers: {
      "Content-Type": "text/plain; charset=utf-8",
      // No Transfer-Encoding: the runtime owns message framing, and
      // the header is illegal over HTTP/2 anyway. No Cache-Control
      // either: Webflow Cloud always replaces it with
      // `private, no-cache`.
    },
  });
}

El punto arquitectónico clave aquí es el streamPromise que no se espera (non-awaited). La ruta inicia el flujo en segundo plano y luego devuelve inmediatamente un Response con el extremo readable como cuerpo.

El navegador recibe los encabezados de respuesta de inmediato y luego recibe los fragmentos de tokens a medida que el flujo en segundo plano los escribe en el writer. Este es el patrón de Web Streams para streaming enviado desde el servidor; funciona de forma nativa en el tiempo de ejecución de Cloudflare Workers porque TransformStream y ReadableStream son ambas APIs web.

Creación del componente de chat del lado del cliente

El cliente lee la respuesta de streaming utilizando la interfaz ReadableStream de la API Fetch. Este componente gestiona el estado del mensaje y renderiza los tokens a medida que llegan:

// app/components/Chat.tsx
"use client";

import { useState } from "react";

type Message = {
  role: "user" | "assistant";
  content: string;
};

export default function Chat() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");
  const [isStreaming, setIsStreaming] = useState(false);

  async function sendMessage() {
    if (!input.trim() || isStreaming) return;

    const userMessage: Message = { role: "user", content: input };
    const newMessages = [...messages, userMessage];
    setMessages(newMessages);
    setInput("");
    setIsStreaming(true);

    // Add an empty assistant message that will be filled by the stream
    setMessages((prev) => [...prev, { role: "assistant", content: "" }]);

    // Webflow Cloud mounts your app at a base path. Server route
    // definitions get it automatically; client-side fetches do not,
    // so a bare "/api/chat" hits the parent Webflow site and 404s.
    const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? "";

    const response = await fetch(`${basePath}/api/chat`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ messages: newMessages }),
    });

    if (!response.ok || !response.body) {
      // Without this, an error JSON from the route is streamed
      // straight into the transcript and rendered as if the
      // assistant said it.
      setIsStreaming(false);
      throw new Error(`Chat request failed: ${response.status}`);
    }

    const reader = response.body.getReader();
    const decoder = new TextDecoder();

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      const token = decoder.decode(value, { stream: true });

      setMessages((prev) => {
        const updated = [...prev];
        updated[updated.length - 1] = {
          role: "assistant",
          content: updated[updated.length - 1].content + token,
        };
        return updated;
      });
    }

    setIsStreaming(false);
  }

  return (
    <div>
      <div>
        {messages.map((msg, i) => (
          <div key={i}>
            <strong>{msg.role === "user" ? "You" : "AI"}:</strong>
            <span>{msg.content}</span>
          </div>
        ))}
      </div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={(e) => e.key === "Enter" && sendMessage()}
        placeholder="Ask anything..."
        disabled={isStreaming}
      />
      <button onClick={sendMessage} disabled={isStreaming}>
        {isStreaming ? "Thinking..." : "Send"}
      </button>
    </div>
  );
}

Este componente envía el historial completo de la conversación (matriz messages ) a la ruta de la API en cada solicitud. El modelo recibe el contexto completo de la conversación, que es como mantiene el hilo de la misma.

Cada mensaje incluye tanto la pregunta del usuario como las respuestas previas del asistente. En el paso 4, este historial de conversación se guarda en Supabase para que no se pierda al actualizar la página.

4. Almacenar y recuperar el historial de conversación con Supabase

Sin persistencia, tu chat de IA empieza de cero cada vez que se carga la página. Si el usuario actualiza, la conversación desaparece y el modelo olvida lo que se ha dicho. Para una función de IA en un producto (un asistente de soporte, una guía de bienvenida o un asesor personalizado), la continuidad de la conversación es lo que convierte una demo en algo a lo que los usuarios realmente vuelven.

Supabase gestiona esto con una única tabla que almacena el historial de mensajes por sesión. Cada mensaje (tanto del usuario como del asistente) se escribe en la tabla y el historial de la conversación se carga al renderizar la página inicialmente.

Como el cliente de JavaScript de Supabase utiliza la API Fetch internamente, funciona en el entorno de ejecución de Cloudflare Workers sin necesidad de ninguna configuración especial.

Configuración de la tabla de Supabase

En tu panel de control de Supabase, crea una tabla llamada messages con las siguientes columnas

create table messages (
  id uuid default gen_random_uuid() primary key,
  session_id text not null,
  role text not null check (role in ('user', 'assistant')),
  content text not null,
  created_at timestamp with time zone default now()
);

-- Index for fast session lookups
create index messages_session_id_idx on messages (session_id, created_at);

-- Lock the table down in the same migration that creates it.
alter table public.messages enable row level security;
revoke all on table public.messages from anon, authenticated;

La columna session_id vincula los mensajes a una conversación específica. Puedes usar un UUID generado por el navegador, un ID de usuario de tu sistema de autenticación o cualquier identificador estable que represente "una conversación".

Para demos sin autenticación, un UUID almacenado en sessionStorage funciona bien. Para aplicaciones con autenticación, usa el ID del usuario que ha iniciado sesión en Supabase Auth, lo que mantiene el identificador de sesión y el sistema de autenticación en un mismo lugar.

Esas dos últimas líneas no son opcionales, y son la razón de ser de esta sección. La Seguridad a nivel de fila (RLS) solo se aplica automáticamente a las tablas creadas a través del editor de tablas del panel de control. Una tabla creada con SQL puro, que es lo que acabas de hacer, comienza con la RLS desactivada y, en proyectos con permisos predeterminados, una tabla nueva en public anon otorga select, insert, update y delete en cada fila.

Si lo lanzas tal cual, cualquier visitante que tenga tu clave pública puede leer y borrar todas las conversaciones de la base de datos.

Deben alinearse dos mecanismos distintos. Los permisos deciden si un rol puede tocar la tabla en absoluto; las políticas deciden qué filas. Añadir una política no revoca un permiso, por eso existe el comando revoke.

Para una demo, vuelve a conceder las operaciones limitadas y escribe una política por cada operación. Para una aplicación en producción, añade una columna user_id uuid references auth.users y filtra por auth.uid(), en lugar de reutilizar session_id para ese fin.

Instalación y configuración del cliente de Supabase

Instala el cliente de JavaScript de Supabase:

npm install @supabase/supabase-js

Crea una fábrica de clientes compartida en lib/supabase.ts. Esto utiliza el mismo patrón de función de fábrica de la guía full-stack. El cliente se instancia dentro de una función para asegurar que las variables de entorno se lean en el momento de la solicitud:

// lib/supabase.ts
import { createClient } from '@supabase/supabase-js';

export function getSupabaseClient() {
  // No NEXT_PUBLIC_ prefix: this factory only ever runs in route
  // handlers, and the prefix would inline the values into the
  // browser bundle for no reason.
  const supabaseUrl = process.env.SUPABASE_URL;
  const supabaseKey = process.env.SUPABASE_PUBLISHABLE_KEY;

  if (!supabaseUrl || !supabaseKey) {
    throw new Error('Missing Supabase environment variables');
  }

  return createClient(supabaseUrl, supabaseKey);
}

Añade ambas variables en la sección Settings → Variables de tu proyecto de Webflow Cloud:

SUPABASE_URL=https://[your-project-ref].supabase.co
SUPABASE_PUBLISHABLE_KEY=sb_publishable_...

Usa la clave sb_publishable_..., no el antiguo anon JWT. Supabase retirará las claves anon y service_role a finales de 2026 en favor de las claves públicas y secretas, así que empieza una nueva compilación con el par actual. (El anon rol de Postgres sigue existiendo —es a lo que equivale una clave pública para un visitante que no ha iniciado sesión—, que es por lo que ambos nombres suelen confundirse).

Añadir rutas para el historial de conversación

Dos controladores de ruta adicionales gestionan la lectura y escritura del historial de mensajes. El primero carga el historial de la conversación para una sesión determinada.

El segundo guarda un mensaje después de haber sido enviado o recibido:

// app/api/history/route.ts
import { getSupabaseClient } from "@/lib/supabase";
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const sessionId = searchParams.get("session_id");

  if (!sessionId) {
    return NextResponse.json({ error: "session_id is required" }, { status: 400 });
  }

  const supabase = getSupabaseClient();

  const { data, error } = await supabase
    .from("messages")
    .select("role, content")
    .eq("session_id", sessionId)
    .order("created_at", { ascending: true });

  if (error) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }

  return NextResponse.json(data);
}

export async function POST(request: NextRequest) {
  const { session_id, role, content } = await request.json();

  if (!session_id || !role || !content) {
    return NextResponse.json(
      { error: "session_id, role, and content are required" },
      { status: 400 }
    );
  }

  const supabase = getSupabaseClient();

  const { error } = await supabase
    .from("messages")
    .insert({ session_id, role, content });

  if (error) {
    return NextResponse.json({ error: error.message }, { status: 500 });
  }

  return NextResponse.json({ success: true });
}

La ruta GET de historial devuelve los mensajes ordenados de forma created_at ascendente (los más antiguos primero), que es el orden en el que deben pasarse al messages array de OpenAI.

El manejador de la ruta de chat (del paso 3) ya acepta un messages array; tu componente cliente carga el historial desde esta ruta al montarse, rellena el messages estado y luego pasa el array completo en cada envío posterior.

Integrar el historial en el flujo de chat cambia la sendMessage función de tu componente cliente: carga el historial al montarse mediante GET {basePath}/api/history?session_id=..., escribe cada mensaje nuevo mediante POST {basePath}/api/history después de enviarlo y cuando finaliza el stream, y pasa siempre la lista completa de mensajes acumulados a POST {basePath}/api/chat.

Cada llamada del lado del cliente necesita el prefijo de la ruta base; las definiciones de las rutas del servidor no.

Con ese bucle en marcha, la IA mantiene el contexto durante toda la sesión y a través de las actualizaciones de página.

5. Despliega en Webflow Cloud y configura las variables de producción

Una vez que el streaming funcione en la vista previa local y el historial de conversación se escriba correctamente en Supabase, despliega a producción:

webflow cloud deploy

Este comando ejecuta la compilación completa de OpenNext, prepara la aplicación Next.js para el entorno de ejecución de Cloudflare Workers y sube el worker y los recursos estáticos a Webflow Cloud. La CLI muestra una URL de despliegue al finalizar. El primer despliegue tarda más debido a la carga inicial de recursos; los siguientes son más rápidos.

Verificación de todas las variables de entorno antes del despliegue

Antes de ejecutar el comando de despliegue, confirma que estas variables estén configuradas en Ajustes → Variables de tu proyecto en Webflow Cloud:

# OpenAI
OPENAI_API_KEY

# Supabase
SUPABASE_URL
SUPABASE_PUBLISHABLE_KEY

Las variables de entorno configuradas solo en .env.local no se leen durante la compilación de Webflow Cloud ni en tiempo de ejecución en el worker desplegado. Si falta una variable, las cláusulas de protección en las funciones de fábrica (if (!apiKey) throw new Error(...)) mostrarán el fallo inmediatamente en los registros de despliegue en lugar de generar un error críptico más adelante.

Monitorización del streaming en producción

Después de desplegar, prueba el comportamiento del streaming desde la URL en vivo, no solo desde npm run preview. El entorno de ejecución de Workers en producción a veces se comporta de forma ligeramente distinta a la simulación local de Wrangler cuando hay conexiones de streaming involucradas, especialmente en lo que respecta a los tiempos de espera de conexión.

El tiempo de espera predeterminado de OpenAI para las solicitudes de streaming es de 10 minutos. En la práctica, la mayoría de las respuestas de streaming se completan en menos de 30 segundos, pero si usas la API para generar textos más largos, conviene tener en cuenta el límite de ejecución de Cloudflare Workers (que escala con la solicitud).

¿Qué hace que las aplicaciones de IA en Webflow Cloud fallen?

La mayoría de los fallos en las aplicaciones de IA de Webflow Cloud se dividen en cuatro categorías: variables de entorno faltantes o indefinidas, caídas en la conexión de streaming, contexto de conversación demasiado grande para la ventana de contexto del modelo y bloqueos de lectura o escritura por parte de RLS de Supabase.

Aquí tienes el diagnóstico para cada uno.

La respuesta de streaming comienza, pero se corta a mitad de la generación

Causa: La causa más común es un tiempo de espera del lado del cliente, no del servidor. Si la llamada fetch del cliente tiene un AbortSignal o un tiempo de espera personalizado configurado, las respuestas largas pueden cortarse antes de que el stream se cierre de forma natural. Otra causa es alcanzar el límite de ejecución de Cloudflare Workers, aunque para respuestas conversacionales gpt-4o-mini estándar, esto rara vez es el problema.

Solución: Verifica que tu llamada fetch del cliente no tenga un tiempo de espera explícito. El comportamiento predeterminado de fetch en el navegador no tiene tiempo de espera, que es justo lo que necesitas para el streaming. Si añadiste una señal de cancelación, asegúrate de que solo se active con una acción del usuario (como un botón de "Detener"), no mediante un temporizador.

En el lado del servidor, cierra el flujo en todas las rutas. Ponerlo en un bloque hace precisamente eso: una interrupción a mitad de la generación termina la respuesta en lugar de dejar al navegador esperando.writer.close()finally

No llames a después, ya que abortar un escritor que ya está cerrado provoca un rechazo y te dará un segundo error, más confuso, además del primero.writer.abort()

OPENAI_API_KEYno está definido en el worker desplegado

Causa: La variable existe en pero no en Configuración → Variables del proyecto de Webflow Cloud. Este es el problema de producción más común con las integraciones de Webflow Cloud. El archivo solo se lee durante y localmente; nunca se despliega ni es leído por el worker de producción..env.local.env.localnpm run devnpm run preview

Solución: Añade a Configuración → Variables de tu proyecto de Webflow Cloud y vuelve a desplegar. El mensaje de error de la cláusula de guarda ( aparecerá en los registros de despliegue de Webflow Cloud si los revisas tras una solicitud fallida.OPENAI_API_KEY"Missing OPENAI_API_KEY environment variable")

OpenAI devuelve un error tras conversaciones largascontext_length_exceeded

Causa: La matriz que se pasa a OpenAI contiene el historial completo de la conversación, y una conversación lo suficientemente larga terminará superando la ventana de contexto del modelo.messages

Esa ventana es ahora grande (1,05 millones de tokens en los modelos GPT-5.6), por lo que es mucho menos probable que en generaciones anteriores, pero una matriz sin límites en una sesión larga acabará alcanzándola, y se vuelve costosa mucho antes de volverse fatal.

Solución: Antes de enviar los datos a OpenAI, recorta el historial de la conversación a una ventana reciente. Por ejemplo, mantén el mensaje del sistema más los últimos 20 intercambios. Un enfoque sencillo es recortar la matriz de mensajes desde el manejador de rutas antes de pasarla a la API.

Alternativamente, utiliza un paso de resumen que condense el historial anterior de la conversación en un único mensaje de contexto. Ninguno de los dos enfoques es complicado, pero debes gestionarlo a nivel de ruta, no en el componente del cliente.

Las consultas de historial de Supabase devuelven matrices vacías a pesar de que existen mensajes

Causa: La seguridad a nivel de fila (Row Level Security) está activada en la tabla y el rol anon no tiene una política. Esto devuelve un resultado vacío sin error; un comportamiento idéntico al de una tabla vacía, lo que hace que parezca un error de lógica en lugar de un problema de permisos.messagesSELECT

Solución: Comprueba en este orden, ya que la causa más probable es la que la gente suele saltarse. Primero confirma los permisos: un permiso revocado genera un error antes de consultar cualquier política. Luego confirma que RLS esté realmente activado, ya que una tabla SQL sin procesar comienza desactivada y una tabla abierta devuelve filas en lugar de ocultarlas. Solo entonces revisa las políticas.42501

Escribe una política por operación en lugar de una única . Para una demostración pública, una política con una condición es suficiente, siempre y cuando entiendas que otorga a cada visitante acceso de lectura a todas las filas.for allselecttrue

Para el acceso específico del usuario, no filtres : es una columna y devuelve un , por lo que la comparación falla por el tipo, y conceptualmente una sesión es una conversación, no una persona. Añade una columna y filtra basándote en ella.session_id = auth.uid()session_idtextauth.uid()uuiduser_id uuid references auth.users

Construye más sobre tu base de IA

Esta guía cubrió la configuración completa para una función de chat con IA en streaming en Webflow Cloud: inicializar el cliente de OpenAI para el entorno de ejecución de Workers, transmitir tokens al navegador a través de los manejadores de rutas de Next.js y persistir el historial de la conversación en Supabase.

Una vez que la función funciona, la siguiente pregunta es si sus respuestas son buenas, que es para lo que sirve nuestra guía de evaluación de Braintrust. La otra extensión obvia es añadir autenticación. Una vez que sepas quién está preguntando, puedes filtrar su historial de conversación desde Supabase, personalizar el mensaje del sistema con los datos de su cuenta y restringir el acceso a la IA mediante un muro de pago.

Para la referencia de integración de Webflow + ChatGPT, consulta la página de integración de Webflow y ChatGPT. Para Supabase, la página de Webflow y Supabase cubre la conexión a la base de datos con más detalle.

Preguntas frecuentes

¿Requiere el SDK de OpenAI alguna configuración especial para funcionar en Webflow Cloud?

No. El SDK de OpenAI utiliza la API Fetch internamente y funciona en Cloudflare Workers sin configuración adicional. (Las guías antiguas comparaban esto con Stripe, que necesitaba httpClient: Stripe.createFetchHttpClient(); la versión actual stripe-node incluye una compilación para workers que usa fetch por defecto, por lo que esa anulación ya no es necesaria). Lo inicializas con new OpenAI({ apiKey: process.env.OPENAI_API_KEY }) y lo usas exactamente igual que en un entorno Node.js estándar.

¿Puedo usar el SDK de IA de Vercel en lugar del SDK de OpenAI?

Sí. El SDK de IA de Vercel (paquete ai con el proveedor @ai-sdk/openai) funciona en Cloudflare Workers y ofrece abstracciones de alto nivel como streamText en el servidor y useChat en el cliente. El hook useChat gestiona automáticamente las transmisiones, el estado de los mensajes y los estados de carga, ahorrándote la implementación del lado del cliente descrita en el paso 3.

¿Cómo evito que el historial de la conversación crezca demasiado?

Envía a OpenAI una parte recortada del historial en lugar de la lista completa. Un enfoque práctico es mantener los últimos 10-20 intercambios (20-40 mensajes) de la consulta del historial en Supabase. Para sesiones largas, implementa un resumen: cuando el historial supere un límite, envía los mensajes más antiguos a OpenAI con la instrucción "resume esta conversación", guarda el resumen como un mensaje del asistente y reemplaza los mensajes antiguos con él.

¿Qué modelo debería usar para una función de IA en producción?

Elige entre los tres niveles de GPT-5.6 según tus necesidades: gpt-5.6-luna (0,20 $ por 1M de tokens de entrada), ideal para soporte rápido y preguntas frecuentes; gpt-5.6-terra (2,00 $ por 1M), para un equilibrio entre inteligencia y coste; y gpt-5.6-sol (4,00 $ por 1M), para razonamiento complejo, programación y generación de contenido.

Los modelos antiguos como gpt-4o y gpt-4o-mini siguen funcionando, pero tienen ventanas de contexto más pequeñas y una fecha de corte de conocimiento de octubre de 2023. Consulta la página de obsolescencia de OpenAI para ver el estado actual del soporte.


Última actualización
September 4, 2026
Categoría

Artículos relacionados


verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logoreddit logothe new york times logoideo logoupwork logodiscord logo
verifone logomonday.com logospotify logoted logogreenhouse logoclear logocheckout.com logoreddit logothe new york times logoideo logoupwork logodiscord logo

Empieza gratis

Prueba Webflow todo el tiempo que quieras con nuestro plan Starter gratuito. Compra un plan de sitio de pago para publicar, alojar y desbloquear funciones adicionales.

Empieza, es gratis
Ver demostración

Prueba Webflow todo el tiempo que quieras con nuestro plan Starter gratuito. Compra un plan de sitio de pago para publicar, alojar y desbloquear funciones adicionales.