Depuración de las acciones del servidor en Next.js: errores al desplegar, CORS y límites de carga
Una guía práctica de solución de problemas que explica por qué las acciones del servidor de Next.js y las rutas API fallan en silencio o generan errores enigmáticos, con soluciones concretas para cada caso.
Fuentes: Common Server Action and API Route Errors in Next.js (and How to Fix Them)
Las Acciones de Servidor eliminaron gran parte de la complejidad en la lógica de backend de Next.js: ya no es necesario contar con una ruta API dedicada solo para manejar el envío de formularios. Pero esta comodidad conlleva su propio conjunto de posibles fallos, que no se parecen en nada a los errores que se presentaban en el antiguo sistema Pages Router. A continuación, se detalla qué suele salir mal y cómo solucionarlo.
1. “Error: No se encontró la Acción de Servidor” después de un despliegue
Todo funciona bien en tu máquina, pero se rompe en el momento en que se publica una nueva versión; los usuarios que están en sesión de repente reciben este error.
Causa: Las Acciones de Servidor se compilan con identificadores vinculados a una compilación específica. Si alguien tiene pestaña abierta en el navegador mientras realizas el despliegue, su cliente sigue haciendo referencia a los identificadores de la compilación antigua, y el servidor ya no los reconoce.
Solución: no se puede evitar completamente este problema en usuarios que ya están activos durante una implementación, pero se puede manejar de manera más adecuada en lugar de permitir que ocurra en silencio:
'use client';
import { useTransition } from 'react';
function SubmitButton({ action }) {
const [isPending, startTransition] = useTransition(); async function handleClick() {
try {
await action();
} catch (err) {
if (err.message?.includes('Failed to find Server Action')) {
window.location.reload(); // force a fresh client with current build IDs
}
}
} return <button onClick={() => startTransition(handleClick)}>Submit</button>;
}
Manejar este error específico e indicarle al usuario que actualice la página ofrece una experiencia mucho mejor que un cierre abrupto sin explicación alguna.
2. La acción del servidor no se ejecuta en silencio
Se invoca la acción, pero no ocurre nada y no se muestra ningún error en ningún lugar.
Causa común: faltar la directiva 'use server' al principio de la función, o escribir la acción dentro de un componente del cliente sin marcarla adecuadamente.
// Wrong — no directive
async function saveUser(formData) {
await db.user.update(/* ... */);
}
Solución:
'use server';
export async function saveUser(formData) {
await db.user.update(/* ... */);
}
Si la acción está definida en el mismo archivo que un Componente de Cliente, la directiva debe colocarse al principio de la función misma, y no solo en la parte superior del archivo:
'use client';
export default function Form() {
async function saveUser(formData) {
'use server'; // required here specifically
await db.user.update(/* ... */);
}
return <form action={saveUser}>{/* ... */}</form>;
}
3. “Error: Solo se pueden pasar objetos simples a los Componentes de Cliente”
Esto ocurre cuando el valor devuelto por una Acción del Servidor no es serializable: por ejemplo, una instancia de clase, un Date dentro de una estructura inusual, o un modelo de base de datos que contiene sus propios métodos.
'use server';
export async function getUser(id) {
return await db.user.findUnique({ where: { id } }); // ORM might return a class instance
}
Solución: dar forma manualmente al resultado para convertirlo en un objeto simple:
'use server';
export async function getUser(id) {
const user = await db.user.findUnique({ where: { id } });
return {
id: user.id,
name: user.name,
email: user.email,
createdAt: user.createdAt.toISOString(), // Dates need explicit serialization too
};
}
No entregue directamente el resultado de un ORM a un Componente de Cliente. Primero conviértalo en un objeto simple y serializable.
4. La Acción del Servidor funciona, pero los datos del formulario regresan como undefined
<form action={createPost}>
<input name="title" />
</form>
'use server';
export async function createPost(formData) {
console.log(formData.title); // undefined
}
Causa: el argumento formData que recibes es una instancia nativa de FormData, no un objeto regular de JavaScript, por lo que debes llamar a .get() para extraer los valores de él.
Solución:
'use server';
export async function createPost(formData) {
const title = formData.get('title');
console.log(title); // correct
}
5. “El cuerpo superó el límite de 1 mb” en Server Actions
Server Actions imponen un límite predeterminado para el tamaño del cuerpo de la solicitud, lo cual se convierte en un problema en cuanto trabajas con subidas de archivos o cargas de formularios voluminosas.
Solución: aumenta el límite explícitamente dentro de next.config.js:
// next.config.js
module.exports = {
experimental: {
serverActions: {
bodySizeLimit: '10mb',
},
},
};
Para cualquier situación similar a una verdadera subida de archivo, suele ser mejor omitir por completo el enrutamiento del archivo a través de Server Action. Sube directamente desde el cliente al almacenamiento de objetos —S3, R2 o similar— utilizando una URL firmada. De esta manera se evita el límite de tamaño y se reduce la carga en tu servidor.
6. Errores CORS en rutas de API llamadas desde un origen diferente
Este problema suele aparecer cuando una aplicación móvil, una extensión del navegador o un frontend separado accede directamente a las rutas de API de Next.js en lugar de hacerlo a través de sus propias páginas.
Access to fetch at 'https://yourapp.com/api/data' from origin 'https://otherapp.com' has been blocked by CORS policy
Solución: establecer los encabezados explícitamente dentro del manejador de rutas:
// app/api/data/route.js
export async function GET(request) {
const data = await getData();
return Response.json(data, {
headers: {
'Access-Control-Allow-Origin': 'https://otherapp.com', // never '*' for authenticated routes
'Access-Control-Allow-Methods': 'GET, POST',
},
});
}
export async function OPTIONS() {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': 'https://otherapp.com',
'Access-Control-Allow-Methods': 'GET, POST',
},
});
}
Evite establecer Access-Control-Allow-Origin: '*' en ningún endpoint que dependa de cookies de autenticación. Hacerlo equivale a entregar los datos del usuario a cualquier origen que decida solicitarlos.
7. El manejador de rutas devuelve HTML en lugar de JSON en caso de error
Cuando un manejador de rutas lanza una excepción no manejada, el cliente no recibe un error en formato JSON ordenado, sino la página de error HTML integrada de Next.js. Esto representa un problema para cualquier código del cliente que asuma que puede llamar seguramente a response.json().
Solución: envuelva la lógica del manejador en un try/catch que siempre devuelva JSON:
export async function POST(request) {
try {
const body = await request.json();
const result = await processOrder(body);
return Response.json(result);
} catch (error) {
return Response.json(
{ error: error.message ?? 'Something went wrong' },
{ status: 500 }
);
}
}
Con esto en vigor, el cliente siempre recibe algo que se pueda parsear como JSON, ya sea que la solicitud tenga éxito o fracase, en lugar de un documento HTML que provoque errores en la llamada a .json() más adelante.
Por qué estos errores parecen peores que los bugs de las rutas API de estilo antiguo
Las acciones del servidor combinan la lógica del cliente y del servidor en el mismo archivo, lo que hace sorprendentemente fácil cruzar los límites sin darse cuenta. Un valor de retorno puede parecer perfectamente normal en tu editor y aún así causar problemas en tiempo de ejecución porque no es serializable, o una directiva faltante puede provocar un fallo silencioso en lugar de lanzar la excepción donde uno esperaría. Las habituales prácticas de depuración de las APIs de estilo REST —abrir la pestaña de red e inspeccionar la respuesta— no siempre son útiles aquí, ya que varios de estos fallos ocurren incluso antes de que se envíe cualquier solicitud de red.
Para los equipos que crean paneles de control repletos de formularios, cargas de archivos y mutaciones distribuidas en muchas páginas, vale la pena resolver de manera consistente estos patrones —serialización, manejo de errores y límites de tamaño del cuerpo— en toda la base de código, en lugar de volver a depurar los mismos problemas en cada nuevo formulario. Algunas plantillas administrativas preconstruidas para productos SaaS basados en IA ya vienen con acciones de servidor y rutas API configuradas correctamente, incluyendo validación mediante Zod y un manejo adecuado de errores integrado desde el principio, por lo que estos siete problemas se abordan una sola vez en lugar de reaparecer en cada formulario que se cree.
Conclusión
Las acciones del servidor reducen gran parte de la estructura genérica que antes requerían las rutas API, pero también traen consigo su propio conjunto de modos de fallo: identificadores de compilación que se vuelven obsoletos después de un despliegue, directivas faltantes, valores de retorno que no se pueden serializar, uso incorrecto de FormData, límites en el tamaño del cuerpo, fallos CORS entre orígenes diferentes y respuestas de error que en realidad no son JSON. Todos estos problemas aparecen con frecuencia, se pueden solucionar en solo unas pocas líneas y merecen una verificación rápida antes de asumir que hay algún problema más complicado.