Adios
BlogNext.js SaaS

Next.js SaaS

Cómo ejecutar tareas en segundo plano y webhooks fiables en un SaaS con Next.js

Diseña tareas persistentes, webhooks verificados, reintentos, idempotencia, plazos, estados de fallo y trabajo observable en segundo plano para un SaaS con Next.js.

Equipo de AdiosActualizado 17 de julio de 20268 min de lectura

Una solicitud no es buen lugar para trabajo que pueda durar minutos, dependa de un proveedor poco fiable o necesite sobrevivir al reinicio de un proceso. El estado persistente debe durar más que la solicitud.

Identifica el trabajo que debe salir de la solicitud

Traslada las importaciones, exportaciones, informes, procesamiento multimedia, correo masivo, sincronización con proveedores y otros trabajos lentos a un límite de tarea persistente. La solicitud debe validar la intención, guardar la tarea y devolver un identificador con el que la interfaz muestre su progreso.

No todas las tareas asíncronas necesitan un servicio independiente desde el primer día. Sí necesitan estado persistente, un responsable, un contrato de ejecución y una vía de recuperación. Una promesa no esperada en un Route Handler puede desaparecer al terminar el proceso y dejar al usuario sin un registro fiable.

Modela el ciclo de vida de la tarea

Registra un ID estable, tipo, propietario, referencia de entrada validada, estado, número de intentos, próxima hora de ejecución, referencia del resultado y error sin datos sensibles. Usa estados como queued, running, succeeded, failed y canceled. Una concesión temporal o señal de actividad impide que dos procesos de trabajo posean silenciosamente el mismo intento para siempre.

Guarda los payloads y salidas grandes fuera del registro de cola cuando corresponda. La tarea debe referenciar objetos persistentes y volver a consultar la autorización o propiedad actuales antes de publicar un resultado.

type JobState =
  | "queued"
  | "running"
  | "succeeded"
  | "failed"
  | "canceled";

type Job = {
  id: string;
  tenantId: string;
  type: string;
  state: JobState;
  attempts: number;
  runAfter: Date;
};

Haz idempotente la ejecución

Un proceso de trabajo puede caer después de que una acción externa tenga éxito y antes de registrar el éxito local. Diseña cada paso para repetirlo con seguridad. Usa claves de negocio únicas, claves de idempotencia del proveedor, upserts y puntos de control registrados, en lugar de asumir una entrega exactamente una vez.

Separa la identidad de la tarea de la de cada intento. Los reintentos deben compartir la misma tarea de negocio y registrar pruebas distintas de cada intento. Así la interfaz puede mostrar lo ocurrido sin crear varias exportaciones o correos indistinguibles.

Limita los reintentos y las llamadas externas

Establece plazos de conexión y respuesta para los proveedores. Reintenta los timeouts transitorios, los límites de tasa y los fallos temporales con espera progresiva y variación aleatoria. No reintentes indefinidamente entradas inválidas, autorizaciones revocadas ni rechazos permanentes del proveedor.

Cuando se agoten los intentos permitidos, pasa la tarea a un estado fallido visible y genera alertas según su importancia. Conserva suficiente contexto del error para actuar sin registrar credenciales ni payloads privados.

  • —Clasifica los errores antes de reintentar.
  • —Limita los intentos y el tiempo total transcurrido.
  • —Distribuye los reintentos con variación aleatoria.
  • —Ofrece un reintento manual solo cuando sea seguro repetir la operación.

Protege los webhooks entrantes

Recibe los eventos del proveedor en un Route Handler, verifica la firma con el cuerpo original, rechaza las solicitudes inválidas u obsoletas según el protocolo del proveedor y registra el ID del evento antes de realizar trabajo costoso. Devuelve éxito rápidamente cuando se complete la entrega persistente del trabajo.

Usa una restricción de unicidad para deduplicar entregas. Asocia el evento del proveedor a un tenant o cuenta local mediante metadatos fiables o IDs de proveedor guardados, no mediante un campo de tenant arbitrario del payload.

Muestra a los usuarios el progreso real

Una tarea enviada no está completada. Devuelve su ID y muestra el estado en cola o en ejecución en la aplicación. Actualízalo mediante navegación del servidor, consultas periódicas o un canal en tiempo real adecuado al producto. Haz visibles el fallo y la cancelación, en lugar de dejar un indicador de carga para siempre.

Protege las descargas de resultados con las mismas comprobaciones de propiedad que la solicitud original. Haz caducar los archivos generados cuando corresponda y separa las explicaciones para el usuario de los detalles internos del error.

Prueba interrupciones y entregas duplicadas

Detén un proceso de trabajo a mitad de un paso, reinícialo, entrega dos veces el mismo webhook, retrasa al proveedor más allá del timeout, agota los reintentos y retira el acceso de un usuario antes de terminar. Confirma que la operación sigue siendo segura y que su estado final se entiende.

Prueba el despliegue mientras haya tareas en ejecución. Los procesos de trabajo deben terminar, liberar o reintentar con seguridad el trabajo concedido temporalmente según el diseño. Las migraciones de esquema deben seguir siendo compatibles con las tareas en cola creadas por la versión anterior.

Opera las tareas y los flujos de trabajo junto a la aplicación

Adios puede ejecutar procesos persistentes de aplicación y flujos de trabajo inspeccionables con disparadores, pasos, esperas, aprobaciones e historial de ejecuciones. Centra la solicitud Next.js en la validación y la entrega persistente del trabajo; deja la ejecución con reintentos al proceso de trabajo o al flujo.

Despliega la configuración y las referencias a secretos junto al código fuente, inspecciona por separado los registros de la aplicación y de los flujos de trabajo, y usa comprobaciones de salud para los servicios que reciben tráfico. Esto mantiene estable la versión para clientes mientras los fallos en segundo plano conservan sus propias pruebas y vía de recuperación.

env:
  DATABASE_URL: secret://DATABASE_URL
  EMAIL_API_KEY: secret://EMAIL_API_KEY

runtime:
  name: node@24
  port: 3000
  health_path: /api/health
Todos los artículos