Escritos

Códigos de estado HTTP: referencia completa y las confusiones más comunes

Una lista de referencia completa de los códigos de estado HTTP, más las decisiones realmente difíciles: 301 vs 308, 401 vs 403, 422 vs 400, 502 vs 503, incluido cómo los gestiona Next.js por dentro.

Furkan Özay27 de junio de 2024 · 14 min de lectura
czCZAY.DEV // EDITORIALES
MODERN WEB & REACT

#Web Temelleri · 14 MIN DE LECTURA

FURKAN ÖZAYVOL. 2024 // #CC0E

Memorizar una lista de códigos de estado es fácil; elegir el correcto es lo difícil. La diferencia entre "no pude encontrar este registro" y "no tienes permiso para ver este registro" en una API, que un POST se convierta en GET durante una redirección, que Google elimine una dirección de su índice por una página de error: todo se reduce a elegir el número de tres cifras adecuado.

Este artículo tiene dos partes: primero, una lista completa (úsala como referencia) y después los códigos que más se eligen mal en la práctica y cómo aparecen en Next.js.

Cinco clases

ClaseSignificadoEn breve
1xxInformativaSolicitud recibida, el procesamiento continúa
2xxÉxitoSolicitud procesada correctamente
3xxRedirecciónSe necesita una acción adicional para completarla
4xxError del clienteEl error está en el lado del cliente
5xxError del servidorEl error está en el lado del servidor

La distinción parece simple, pero hay casos límite: el cliente envió una solicitud válida, el servidor funciona correctamente, pero una regla de negocio lo prohíbe. ¿Es 4xx o 5xx? (Respuesta: 4xx. Detalles más abajo.)

1xx — Informativos

CódigoNombreSignificado
100ContinueEl cliente puede seguir enviando el cuerpo de la solicitud
101Switching ProtocolsEl servidor cambia al protocolo indicado en la cabecera Upgrade (handshake de WebSocket)
102ProcessingSolicitud recibida, aún sin resultado (WebDAV)
103Early HintsPista de precarga de recursos mediante la cabecera Link antes de que la respuesta final esté lista

En la práctica, 103 Early Hints es el único 1xx moderno que realmente resulta útil: mientras el servidor prepara la respuesta principal, puede decirle al navegador "empieza a descargar este CSS y esta fuente por adelantado".

2xx — Éxito

CódigoNombreSignificado
200OKLa solicitud tuvo éxito; el significado varía según el método HTTP
201CreatedSe creó un nuevo recurso (normalmente POST/PUT)
202AcceptedRecibida pero aún no procesada, para trabajos asíncronos
203Non-Authoritative InformationLos metadatos no provienen del servidor de origen (poco común; prefiere 200)
204No ContentÉxito, sin cuerpo; las cabeceras pueden ser relevantes
205Reset ContentEl cliente debe restablecer la vista del documento
206Partial ContentRespuesta parcial a una solicitud Range (streaming de vídeo, reanudar descargas)
207Multi-StatusEstado independiente para varios recursos (WebDAV)
208Already ReportedEvita volver a enumerar los miembros (WebDAV)
226IM UsedRespuesta devuelta mediante codificación delta

Consejo: Usa 201 y 204

La mayoría de las APIs devuelven 200 para todo. Pero cuando un endpoint de creación devuelve 201 Created + una cabecera Location, y uno de eliminación devuelve 204 No Content, el cliente sabe de inmediato qué ocurrió sin tener que interpretar un cuerpo.

3xx — Redirección

CódigoNombre¿Conserva el método?¿Permanente?
300Multiple Choices——
301Moved PermanentlyNo (los navegadores cambian POST a GET)Permanente
302FoundNo (POST → GET)Temporal
303See OtherNo: siempre cambia a GETTemporal
304Not Modified— (respuesta en cache)—
305Use Proxy⚠️ Obsoleto—
306(sin uso)⚠️ Reservado—
307Temporary RedirectSíTemporal
308Permanent RedirectSíPermanente

Toda esta tabla se resume en una sola distinción: ¿se conserva el método HTTP después de redirigir?

  • 301 / 302 son códigos heredados. Los navegadores convierten las solicitudes POST en GET con ellos; aunque la especificación no lo dijera estrictamente, es el estándar de facto.
  • 307 / 308 se introdujeron para aclarar esta ambigüedad: el método que se usó se mantiene sin cambios. Si rediriges un POST, se envía como POST.
  • 303 hace justo lo contrario a propósito: fuerza el método a GET sin importar lo que llegó.

Explicación: ¿Para qué sirve 303?

Cuando un usuario envía un formulario, quieres redirigirlo a una página de resultado. Si usas 307, llegará a la página de resultado también con una solicitud POST; si recarga la página, el navegador pregunta "¿quieres reenviar el formulario?" y podrías acabar con registros duplicados. 303 evita esta trampa forzando la solicitud a GET. Este patrón se llama POST/Redirect/GET y es crítico en flujos como los callbacks de pago.

Advertencia: Impacto en SEO

Usa 301/308 para movimientos permanentes; los motores de búsqueda eliminarán la URL antigua de su índice y transferirán la autoridad de los enlaces a la nueva. Si usas 302/307, la URL antigua sigue indexada. Equivocarte en esta elección durante las migraciones de un sitio es una de las causas más comunes de pérdida de tráfico.

4xx — Error del cliente

CódigoNombreSignificado
400Bad RequestLa solicitud está mal formada: sintaxis no válida, el cuerpo no se puede interpretar
401UnauthorizedEn realidad significa "no autenticado": se requiere iniciar sesión
402Payment RequiredSe requiere pago; sin uso estándar
403ForbiddenEl servidor sabe quién eres, pero no tienes permiso
404Not FoundEl recurso no existe, o quieres ocultar su existencia
405Method Not AllowedEl método se reconoce pero no está soportado en este recurso
406Not AcceptableLa negociación de contenido no produjo ningún formato aceptable
407Proxy Authentication RequiredComo 401, pero para un servidor proxy
408Request TimeoutConexión inactiva cerrada por el servidor
409ConflictLa solicitud entra en conflicto con el estado actual del recurso
410GoneEliminado de forma permanente, la dirección ya no existe
411Length RequiredSe requiere la cabecera Content-Length
412Precondition FailedFalló la precondición de una solicitud condicional
413Content Too LargeEl cuerpo de la solicitud supera los límites del servidor
414URI Too LongLa URL de destino es demasiado larga
415Unsupported Media TypeEl formato del payload de la solicitud no está soportado
416Range Not SatisfiableNo se puede cumplir el rango solicitado
417Expectation FailedNo se pudo cumplir la expectativa de la cabecera Expect
418I'm a teapotCódigo de broma (RFC 2324): las teteras no pueden preparar café
421Misdirected RequestSolicitud enviada al servidor equivocado
422Unprocessable ContentLa sintaxis es correcta, pero el contenido semántico no es válido
423LockedEl recurso está bloqueado (WebDAV)
424Failed DependencyFalló una solicitud anterior (WebDAV)
425Too EarlySe niega a procesar una solicitud que podría ser reproducida (early data de TLS 1.3)
426Upgrade RequiredEl cliente debe actualizar el protocolo
428Precondition RequiredSe requiere una solicitud condicional; evita el problema de la "actualización perdida"
429Too Many RequestsSe superó el límite de solicitudes (rate limit)
431Request Header Fields Too LargeLas cabeceras son demasiado grandes
451Unavailable For Legal ReasonsAcceso bloqueado por motivos legales

¿401 o 403?

La pareja que más se confunde. Sus nombres inducen a error, porque 401 en realidad significa "no autenticado":

  • 401 — No sé quién eres. Inicia sesión. (Sin sesión o con un token no válido.)
  • 403 — Sé quién eres, pero no puedes acceder a este recurso. (Un usuario con el rol user intentando entrar a /admin.)

Consejo: Una tercera opción: 404

A veces incluso 403 revela demasiado. Decir "este recurso existe, pero no puedes verlo" filtra información. Devolver 404 para ocultar por completo la existencia de un recurso, como hace GitHub con los repositorios privados, es un enfoque legítimo.

¿400 o 422?

  • 400 — No pude interpretar la solicitud. JSON corrupto, falta un campo obligatorio, tipo de dato incorrecto.
  • 422 — La interpreté y el formato es válido, pero el significado no lo es. "La fecha de fin no puede ser anterior a la de inicio", "este correo ya está registrado".

En otras palabras: un error de sintaxis es 400, una violación de una regla de negocio es 422. En la práctica, muchas APIs devuelven 400 para ambos, lo cual está bien; pero si los separas, las aplicaciones cliente pueden distinguir "corrige el formulario" de "corrige el formato de la solicitud" sin código adicional.

409 y 410

  • 409 Conflict — La solicitud entra en conflicto con el estado actual del recurso. Un conflicto de versiones por edición concurrente, o intentar crear un recurso con un slug que ya existe.
  • 410 Gone — El recurso existió, se eliminó de forma permanente y no volverá. La diferencia con 404 es la certeza: los motores de búsqueda desindexan un 410 mucho más rápido. Si no estás seguro, quédate con 404.

429 y Retry-After

Si implementas rate limiting, devolver solo 429 es la mitad del trabajo. Envía también una cabecera Retry-After, para que el cliente sepa cuánto esperar en lugar de reintentar a ciegas.

Ejemplo: Rate limiting en un Route Handler de Next.js

TypeScript
export async function POST(req: Request) {
	const allowed = await rateLimit(req);
 
	if (!allowed) {
		return new Response(
			JSON.stringify({ error: "Çok fazla istek gönderdiniz." }),
			{
				status: 429,
				headers: {
					"Content-Type": "application/json",
					"Retry-After": "60", // seconds
				},
			}
		);
	}
 
	// ...
}

451

Se usa cuando el contenido se bloquea por motivos legales. Debe su nombre a la novela Fahrenheit 451 de Ray Bradbury, la temperatura a la que arde el papel.

5xx — Error del servidor

CódigoNombreSignificado
500Internal Server ErrorError genérico; el servidor encontró una condición inesperada
501Not ImplementedEl servidor no soporta el método de la solicitud
502Bad GatewayEl proxy recibió una respuesta no válida del servidor upstream
503Service UnavailableEl servidor no puede atender la solicitud temporalmente (mantenimiento, sobrecarga)
504Gateway TimeoutEl servidor upstream no respondió a tiempo
505HTTP Version Not SupportedVersión de HTTP no soportada
506Variant Also NegotiatesError de configuración del servidor (negociación circular)
507Insufficient StorageEspacio de almacenamiento agotado (WebDAV)
508Loop DetectedSe detectó un bucle infinito (WebDAV)
510Not ExtendedLa extensión HTTP requerida no está soportada
511Network Authentication RequiredEl cliente debe autenticarse para obtener acceso a la red (portal wifi)

502, 503, 504: ¿qué fallo es cada uno?

Estos tres son la forma más rápida de diagnosticar problemas de despliegue en el servidor:

  • 502 Bad Gateway — Nginx está activo, pero la app que hay detrás se ha caído o no responde en ese puerto. Revisa los logs de tu app.
  • 503 Service Unavailable — El servidor está activo, pero se niega a atender tráfico ahora mismo: modo mantenimiento, sobrecarga del sistema, ninguna instancia disponible en el pool. Debe enviarse con Retry-After y no debe cachearse.
  • 504 Gateway Timeout — La app upstream existe pero es demasiado lenta; el proxy dejó de esperar. Busca consultas lentas o llamadas lentas a APIs externas.

Precaución: No devuelvas 200 en modo mantenimiento

Servir una página de "Estamos en mantenimiento" con estado 200 engaña a los motores de búsqueda para que piensen que esa página es tu contenido real. Las páginas de mantenimiento deben devolver 503: los motores de búsqueda no penalizarán tu posicionamiento por interrupciones breves.

Códigos de estado en Next.js

El framework elige algunos códigos de estado por ti, y saber cuáles elige por debajo ayuda.

Importante: Funciones de redirección

  • redirect() → 307 (temporal, conserva el método)
  • permanentRedirect() → 308 (permanente, conserva el método)
  • redirect() dentro del envío de un formulario con Server Action → 303 (para que el navegador continúe con GET; si JS está activo, la navegación del lado del cliente lo gestiona de todos modos)
  • NextResponse.redirect() → por defecto 307

Next.js elige 307/308 en lugar de 302/301 por el problema de conservación del método mencionado antes: un navegador que recibe 302 convierte POST en GET, de modo que un POST a /users se convierte en un GET a /people.

Si vas a hacer un cambio de dirección permanente, usa permanentRedirect() en lugar de redirect(), o define permanent: true dentro de la configuración redirects() de tu next.config.ts. De lo contrario, los motores de búsqueda no desindexarán la dirección antigua.

Llamar a notFound() devuelve 404 y renderiza tu archivo not-found.tsx.

Ejemplo: Códigos de estado correctos en un Route Handler

TypeScript
import { NextResponse } from "next/server";
 
export async function POST(req: Request) {
	const body = await req.json().catch(() => null);
 
	if (!body) {
		return NextResponse.json({ error: "Geçersiz JSON" }, { status: 400 });
	}
 
	const parsed = schema.safeParse(body);
	if (!parsed.success) {
		// Valid format, invalid meaning
		return NextResponse.json(
			{ error: "Doğrulama hatası", issues: parsed.error.issues },
			{ status: 422 }
		);
	}
 
	const session = await auth();
	if (!session) {
		return NextResponse.json({ error: "Giriş gerekli" }, { status: 401 });
	}
	if (session.user.role !== "admin") {
		return NextResponse.json({ error: "Yetkiniz yok" }, { status: 403 });
	}
 
	const created = await db.post.create({ data: parsed.data });
 
	return NextResponse.json(created, {
		status: 201,
		headers: { Location: `/api/posts/${created.id}` },
	});
}

Guía rápida de decisión

Resumen: ¿Qué código debo devolver?

  • Registro creado → 201 + Location
  • Eliminado, nada que devolver → 204
  • URL cambiada de forma permanente → 308 (301 también es aceptable para SEO)
  • Redirigir a la página de resultado tras enviar un formulario → 303
  • No autenticado → 401
  • Autenticado pero sin autorización → 403
  • Hay que ocultar la existencia del registro → 404
  • JSON corrupto → 400
  • JSON válido, violación de una regla de negocio → 422
  • Conflicto de edición concurrente → 409
  • Límite de solicitudes superado → 429 + Retry-After
  • En mantenimiento → 503 + Retry-After
  • Error inesperado → 500 (escribe el motivo real en los logs, no al usuario)

Una última nota: nunca expongas stack traces a los usuarios en las respuestas de error. Los códigos de estado le dicen al cliente qué acción tomar; los detalles granulares pertenecen a los logs del servidor.

Nota: Fuentes

La lista de códigos de estado se basa en la referencia de códigos de estado de respuesta HTTP de MDN, y los comportamientos de Next.js siguen la documentación de redirect.

Hoja de ruta y herramienta interactivas

Sigue leyendo