Redirecciones: redirects y Astro.redirect
Dos maneras de mudar una URL sin romper los enlaces que apuntan a ella: la clave redirects de la configuración, declarativa y resuelta antes del despliegue, y Astro.redirect en el servidor, imperativa y atada a la petición. Y el código de estado como portador de intención: 301 permanente, 302 temporal, 307 que además preserva el método HTTP.
Una URL es un compromiso: alguien la guardó, la enlazó, la compartió. Cuando la estructura del sitio cambia —renombras una sección, unificas dos páginas, mueves un producto—, ese compromiso no puede romperse sin coste. La redirección es el mecanismo que honra la dirección vieja llevándola a la nueva, y Astro la ofrece por dos vías complementarias: redirects, un mapa declarativo que vive en la configuración y se resuelve sin escribir lógica, y Astro.redirect, una decisión imperativa que el servidor toma en el instante de cada petición. Encima de ambas planea la cuestión que separa al aficionado del profesional: el código de estado, que no es un número burocrático sino un mensaje exacto a navegadores y buscadores sobre qué significa esta mudanza.
- Declarar redirecciones estáticas con la clave
redirectsdeastro.config. - Emitir redirecciones dinámicas desde el servidor con
Astro.redirect. - Elegir el código correcto entre 301, 302 y 307 según intención y método.
- Distinguir cómo se materializa cada redirección en estático y bajo demanda.
redirects: el mapa declarativo en la configuración
Cuando la mudanza es masiva, estable y no depende de nada que ocurra en la petición, no hay razón para escribir código: se declara. La clave redirects de astro.config es un objeto que mapea rutas de origen a rutas de destino, y Astro lo evalúa antes de intentar casar cualquier página de src/pages. Una entrada aquí tiene, por tanto, prioridad sobre un fichero del mismo nombre.
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
redirects: {
'/blog-viejo': '/blog',
'/tienda/[id]': '/productos/[id]',
'/panel': { status: 302, destination: '/login' },
},
});
Tres formas conviven en ese objeto y conviene distinguirlas. La cadena simple redirige una URL a otra con un 301 —permanente— por defecto. Los segmentos dinámicos entre corchetes reenvían familias enteras conservando el parámetro: /tienda/42 termina en /productos/42, y el [id] viaja de un lado al otro sin que escribas la correspondencia caso por caso. Y la forma de objeto, con status y destination, te deja fijar el código —un 302 para un desvío que quizá revoques mañana—.
Cómo se materializa depende del modo de renderizado, y es un matiz que muerde. En un sitio estático no hay servidor que emita un 301 de verdad en el momento: Astro genera para cada redirección una pequeña página HTML con una metaetiqueta de refresco, o —si tu adaptador lo soporta— un fichero de configuración que el host traduce a redirecciones nativas, más rápidas y correctas. En renderizado bajo demanda, en cambio, cada entrada de redirects se convierte en una respuesta HTTP real, con su código y su cabecera Location.
El destino de una redirección suele ser una ruta absoluta del propio sitio —/nuevo—, pero también puede ser una URL externa completa si mudas a otro dominio. Dos detalles evitan sorpresas: respeta tu política de trailingSlash, porque un destino con o sin barra final que no case con ella puede encadenar una segunda redirección; y recuerda que si defines una base para el sitio, las rutas internas cuelgan de ella. Un destino mal formado no falla ruidosamente: lleva a un sitio ligeramente distinto del que creías, y esos desvíos silenciosos son los más difíciles de cazar.
Astro.redirect: la decisión en tiempo de petición
El mapa declarativo cubre las mudanzas fijas, pero muchas redirecciones dependen de datos que solo existen en la petición: si hay sesión, qué rol tiene el usuario, si un recurso sigue publicado. Para eso, dentro de una página o un endpoint bajo demanda tienes Astro.redirect, que construye una respuesta de redirección que debes retornar.
---
// src/pages/panel.astro
export const prerender = false;
const sesion = Astro.cookies.get('sesion');
if (!sesion) {
return Astro.redirect('/login', 302);
}
---
<h1>Panel privado</h1>
El detalle que más veces cuesta un disgusto es el return. Astro.redirect no interrumpe la ejecución por sí solo: fabrica una respuesta con la cabecera Location apuntando al destino, pero solo surte efecto si la devuelves. Olvidar el return deja que el frontmatter siga corriendo y que la plantilla se pinte igual —el panel privado se renderiza sin sesión, exactamente lo que creías haber impedido—. Piensa en Astro.redirect como en extender un billete de salida: tenerlo en la mano no te saca de la sala; hay que entregarlo.
El segundo argumento es el código de estado, y su ausencia también tiene consecuencias: si no lo indicas, Astro.redirect usa 302 por defecto. Ese valor es sensato para un desvío condicional como el del ejemplo, pero es el equivocado para una mudanza permanente, y elegir a ciegas es dejar que un detalle importante lo decida la inercia.
Astro.redirect no es exclusivo de las páginas: en un endpoint —un fichero que exporta funciones por método HTTP— sirve igual para redirigir desde una API, por ejemplo tras procesar un envío y querer llevar al visitante a una página de confirmación.
// src/pages/api/enviar.ts
export const prerender = false;
export async function POST({ request, redirect }) {
const datos = await request.formData();
await guardar(datos);
return redirect('/gracias');
}
En un endpoint recibes redirect dentro del contexto, y el patrón es idéntico al de una página: construyes la respuesta y la retornas. Lo único que cambia es dónde vive el código; la mecánica del desvío, y la obligación de retornarlo, son las mismas.
Astro.redirect(destino, codigo) devuelve un objeto Response; no lanza, no corta, no salta. Si escribes Astro.redirect('/login') sin return, produces una respuesta que nadie recoge y el render continúa como si nada. Este es el error silencioso más común del enrutado bajo demanda: la lógica de guarda parece correcta, compila, y sin embargo la página protegida se sirve a cualquiera. Ante una redirección que no dispara, la primera sospecha debe ser siempre un return ausente.
Un peligro emparentado es la cadena de redirecciones. Si una regla manda /a a /b y otra manda /b a /c, el cliente hace varios viajes para llegar a destino, y cada salto cuesta latencia; peor aún, un ciclo —/a a /b y /b a /a— deja al navegador dando vueltas hasta que aborta con un error. Al declarar redirects masivos tras una reestructuración, conviene apuntar siempre al destino final directo, no encadenar mudanzas sobre mudanzas que se fueron acumulando con el tiempo.
El código de estado: 301, 302 y 307
Aquí es donde el enrutado avanzado se separa del ingenuo. El número que acompaña a una redirección no es adorno: le dice al cliente qué clase de mudanza es esta, y esa información cambia el comportamiento de cachés, buscadores y del propio navegador. Hay dos ejes independientes que conviene no confundir: permanencia y preservación del método.
En el eje de la permanencia, un 301 dice esto se ha mudado para siempre: los buscadores transfieren la autoridad de la vieja URL a la nueva y la memorizan, y los navegadores cachean el desvío de forma agresiva —a veces tanto que revertirlo cuesta—. Un 302 dice es temporal, no actualices tus enlaces: el recurso vive normalmente en el origen y ahora mismo lo desviamos, pero mañana quizá no.
El segundo eje es más sutil y suele ignorarse. Cuando la petición original es un POST con un cuerpo —el envío de un formulario—, ¿qué método usa el cliente al seguir la redirección? Históricamente, muchos navegadores convierten un 301 o un 302 en un GET, descartando el cuerpo. Para cuando eso no puede pasar existe el 307: un desvío temporal que obliga a repetir el método y el cuerpo intactos, de modo que un POST sigue siendo un POST. Su pareja permanente es el 308. Si alguna vez rediriges el destino de un formulario, 301 y 302 son trampas; 307 y 308 son la respuesta correcta.
---
// redirigir un POST sin degradarlo a GET
export const prerender = false;
if (Astro.request.method === 'POST') {
return Astro.redirect('/procesar', 307);
}
---
301 permanente
Se mudo para siempre. El buscador transfiere autoridad y el navegador cachea con fuerza. No garantiza el metodo.
302 temporal
Desvio pasajero: no actualices tus enlaces. El recurso vive en el origen. Tampoco garantiza el metodo.
307 preserva metodo
Temporal como el 302, pero obliga a repetir metodo y cuerpo. Un POST sigue siendo POST.
308 permanente y fiel
La version permanente del 307: se mudo para siempre y ademas conserva el metodo original.
flowchart TD
P[peticion a una URL vieja] --> R{hay regla de redireccion}
R -->|no| N[sigue el enrutado normal]
R -->|si| C{que codigo devuelve}
C -->|301 o 308| PERM[permanente el cliente memoriza]
C -->|302 o 307| TEMP[temporal no memoriza]
PERM --> M{preserva el metodo}
TEMP --> M
M -->|301 y 302| G[puede degradar a GET]
M -->|307 y 308| K[conserva POST y cuerpo]
style P fill:#89b4fa,color:#11111b
style K fill:#a6e3a1,color:#11111b
style G fill:#f9e2af,color:#11111bElegir bien es, en el fondo, decir la verdad sobre lo que ha pasado. Una reestructuración definitiva pide 301; un mantenimiento o un flujo de acceso pasajero pide 302; el reenvío de una operación con cuerpo pide 307. El código equivocado no rompe la página, pero desinforma a quien te lee: un 302 en una mudanza real impide que los buscadores consoliden tu autoridad en la URL nueva, y un 301 en un desvío temporal deja a los clientes memorizando algo que dejará de ser cierto.
En la práctica, la elección entre los dos mecanismos y el código correcto se reduce a unas pocas preguntas:
- ¿La mudanza es fija y no depende de la petición? Declárala en
redirects. - ¿Depende de datos como la sesión, el rol o el estado del recurso? Emítela con
Astro.redirect. - ¿El cambio es definitivo? Usa
301. ¿Es provisional? Usa302. - ¿Rediriges una operación con cuerpo, un
POST? Preserva el método con307o308.
Es tentador ver 301, 302 y 307 como sabores intercambiables de la misma acción —mandar al cliente a otra parte— y quedarse con el que sea. Pero cada número es una cláusula de un contrato que firmas con toda la maquinaria de la web, y sus consecuencias se despliegan en el tiempo y fuera de tu vista. Cuando emites un 301, no solo mueves a un visitante: le ordenas a Google que reescriba su índice y transfiera años de reputación a la URL nueva, y le pides al navegador que no vuelva a molestarte con la vieja, que la sustituya en su memoria. Eso es enorme, y es irreversible en la práctica: revertir un 301 que los clientes ya cachearon es una de las tareas más frustrantes de la administración web, porque estás luchando contra decisiones que otros tomaron confiando en tu palabra. El 302 es la humildad codificada: reconoce que no sabes si esto durará, y pide que nadie se comprometa. Y el 307 toca un eje ortogonal, el de la integridad de la operación: dice que la mudanza no debe alterar la naturaleza de lo que el cliente intentaba hacer, que un envío de datos siga siendo un envío de datos y no se degrade a una simple lectura. Detrás de estos tres números hay una idea que trasciende Astro: en un sistema distribuido, comunicar qué tipo de cambio es cada cambio vale tanto como el cambio mismo, porque hay agentes autónomos —cachés, robots, navegadores— tomando decisiones duraderas a partir de tu señal. Redirigir sin pensar el código es como mudarte y dejar en la puerta un cartel ambiguo: unos entenderán que te fuiste para siempre, otros que volverás en una hora, y pasarán meses hasta que todos converjan en la verdad. Dominar las redirecciones no es saber la sintaxis de redirects o de Astro.redirect; es entender que cada desvío es una afirmación sobre el futuro, y elegir el número que dice exactamente lo que sabes y no más.
- Declara en
redirectsun desvío de/precios-viejoa/preciosy comprueba que la entrada gana a un fichero del mismo nombre ensrc/pages. - Añade una redirección dinámica con
[id]y verifica que el parámetro se conserva entre origen y destino. - En una página bajo demanda, protege una ruta con
Astro.redirect('/login', 302)y, a propósito, olvida elreturn; observa que la página protegida se sirve igual y corrige el fallo. - Rediriges el destino de un formulario
POST: usa307en lugar de302y razona por qué degradar el método aGETperdería el cuerpo del envío.