wandres.dev
ROUTING AVANZADO · redirects, paginación, rewrites

Prioridad y orden de rutas

Una misma URL puede encajar en varios ficheros a la vez: una estática, una con parámetro y un catch-all podrían reclamar la misma dirección. Astro no lo deja al azar: ordena las rutas por especificidad con reglas deterministas. Cómo lo específico vence a lo general, cómo se resuelven los empates y qué ocurre cuando entran en juego varios parámetros en la misma ruta.

⏱ 17 min

El enrutado por ficheros tiene una tensión latente: nada impide que dos rutas distintas casen con la misma URL. Un fichero estático blog/destacado.astro, uno con parámetro blog/[slug].astro y un catch-all blog/[...rest].astro podrían, los tres, atender la petición de /blog/destacado. Si el sistema eligiera al azar, el enrutado sería impredecible y frágil. Astro lo impide con una regla que has visto asomar por todo este nivel y que aquí se formaliza: cuando varias rutas compiten, gana la más específica. Detrás de esa frase hay un orden total, determinista y razonable de antemano, que decide cada colisión sin ambigüedad. Dominar el enrutado avanzado es, en el fondo, tener ese orden tan interiorizado que puedas predecir qué fichero atenderá cualquier URL antes de arrancar el servidor.

🎯 Al terminar esta lección sabrás
  • Reconocer cuándo varias rutas pueden casar con la misma URL.
  • Aplicar la jerarquía estática mayor que nombrada mayor que catch-all.
  • Resolver colisiones con varios parámetros comparando segmento a segmento.
  • Anticipar los desempates entre rutas prerenderizadas, de servidor e inyectadas.

Cuando dos rutas casan la misma URL

La raíz del problema es que un patrón dinámico, por definición, abarca muchas URLs concretas, y algunas de esas URLs concretas pueden tener además su propio fichero específico. Considera esta carpeta:

src/pages/blog/
  destacado.astro       casa exactamente con /blog/destacado
  [slug].astro          casa con /blog/cualquier-cosa
  [...rest].astro       casa con /blog y todo lo que cuelgue

Para la petición /blog/destacado, los tres ficheros son candidatos válidos: el estático porque coincide literalmente, el de parámetro porque destacado es un valor posible de slug, y el catch-all porque su cola absorbe cualquier profundidad. Sin una regla, tendrías tres respuestas posibles y ninguna forma de saber cuál llega. La pregunta quién atiende esta URL necesita una respuesta única, y esa unicidad es lo que la prioridad de rutas garantiza.

Conviene separar dos preguntas que es fácil mezclar. Una es qué rutas casan con una URL, que puede ser un conjunto de varias candidatas; otra es cuál de ellas la atiende, que es siempre una sola. El casado es una relación de potencialmente muchos a uno; la prioridad es la función que colapsa ese conjunto en la única elegida. Todo lo que sigue describe cómo se calcula esa elección y por qué su resultado es predecible antes de arrancar nada.

ℹ️
Colisión no es error

Que varias rutas casen con una URL no es un fallo que Astro deba reportar; es una situación normal y a menudo deseada. Quieres, muy a propósito, un [slug].astro que atienda el caso general y un destacado.astro que dé un trato especial a una entrada concreta. La coexistencia es la funcionalidad; lo que hace falta no es prohibirla, sino una política clara de desempate que decida, cada vez, cuál de las candidatas gana. Astro provee esa política y la aplica sola.

La jerarquía de especificidad

El principio rector es que lo más concreto gana sobre lo más general, y se concreta en una escalera de tres peldaños que ya has recorrido a pedazos por todo el nivel. De mayor a menor prioridad:

  • Las rutas estáticas, sin ningún parámetro, son lo más específico y ganan siempre. blog/destacado.astro describe exactamente una URL, así que cuando esa URL se pide, no hay candidata más precisa.
  • Las rutas con parámetro nombrado, con un [slug], ocupan el peldaño intermedio. Casan con un segmento cualquiera, pero solo con uno, así que son más específicas que un comodín aunque menos que una coincidencia literal.
  • Las rutas catch-all, con un [...rest], son la red final. Absorben cero o más segmentos de profundidad indefinida, la máxima generalidad, y por eso solo reciben lo que ninguna ruta más concreta reclamó.

Así, /blog/destacado lo atiende destacado.astro; /blog/otra-cosa lo atiende [slug].astro, porque no hay fichero literal para esa URL pero sí encaja en el parámetro; y /blog/una/ruta/profunda cae en [...rest].astro, la única capaz de tragar varios segmentos. Cada URL encuentra a su candidata más específica, y las demás se apartan.

Que el criterio sea la especificidad no es arbitrario: codifica una intuición muy sensata sobre la intención del autor. Quien se molesta en describir una URL con precisión —escribiendo su nombre literal— probablemente sabe mejor qué mostrar en ella que quien la cubre con un comodín amplio. La prioridad no impone un capricho del framework; formaliza lo que un humano razonable esperaría al mirar la carpeta.

💡
Extiende por adición, no por ramificación

La regla juega a tu favor si la aprovechas. Cuando quieras un trato de excepción para una URL concreta que ya cubre un patrón, no toques el patrón: añade a su lado un fichero más específico. Un blog/[slug].astro que sirve todas las entradas convive sin fricción con un blog/manifiesto.astro que le da a esa una un diseño propio; el estático gana por especificidad y el resto sigue cayendo en el parámetro. Extender el comportamiento por adición, sin llenar el fichero general de condicionales, es una de las comodidades más elegantes del enrutado por ficheros.

flowchart TD
U[una URL entrante] --> S{hay ruta estatica exacta}
S -->|si| WIN1[gana la estatica]
S -->|no| D{hay ruta con param nombrado}
D -->|si| WIN2[gana el param nombrado]
D -->|no| R{hay ruta catch all}
R -->|si| WIN3[gana el catch all]
R -->|no| NF[404 nadie casa]
style U fill:#89b4fa,color:#11111b
style WIN1 fill:#a6e3a1,color:#11111b
style WIN2 fill:#a6e3a1,color:#11111b
style WIN3 fill:#a6e3a1,color:#11111b
style NF fill:#f38ba8,color:#11111b

Por encima de esa escalera hay dos matices que conviene tener presentes. Primero, las reglas de redirects de la configuración se evalúan antes de intentar casar ninguna página, así que una entrada de redirección gana a un fichero del mismo nombre. Y segundo, entre dos rutas dinámicas por lo demás iguales, una prerenderizada —horneada en el build— tiene prioridad sobre una de servidor —resuelta bajo demanda—, de modo que puedes congelar casos concretos y dejar el resto para el render en vivo.

Puesto todo junto, el orden de resolución que Astro aplica, de mayor a menor prioridad, es este:

  • Las reglas de redirects de la configuración, evaluadas antes que cualquier página.
  • Las rutas estáticas sin parámetros, la coincidencia más literal posible.
  • Las rutas con parámetro nombrado, más específicas cuantos más segmentos fijos acompañen al corchete.
  • Entre dos dinámicas equivalentes, las prerenderizadas antes que las de servidor.
  • Las rutas catch-all, la red final que recoge lo que nadie más reclamó.
  • Y, ante un empate perfecto, el orden alfabético como desempate último.

Varios parámetros y los desempates

Cuando las rutas tienen más de un segmento, la comparación deja de ser un único escalón y pasa a hacerse segmento a segmento. Astro recorre las rutas candidatas posición por posición y, en cada una, aplica la misma jerarquía: un segmento estático es más específico que uno con parámetro nombrado, y este más que un catch-all. La ruta que gana es la que resulta más específica según ese barrido.

src/pages/
  [lang]/about.astro        casa con /es/about
  [lang]/[...slug].astro    casa con /es/cualquier/cosa

Para /es/about, ambas rutas casan: el primer segmento es un parámetro [lang] en las dos, pero el segundo es estático about en la primera y un catch-all en la segunda. La primera gana porque, en el segmento donde difieren, ofrece una coincidencia literal frente a un comodín. Puedes así tener una página about propia de cada idioma y, a la vez, un catch-all que atienda el resto del contenido localizado, sin que compitan al azar.

La misma lógica escala a rutas de tres o más segmentos —un archivo por fecha [anio]/[mes]/[dia].astro, una taxonomía [tipo]/[categoria]/[slug].astro—: Astro las compara posición por posición hasta la primera donde una es más específica que la otra, y ahí decide. No hay un límite de parámetros ni un tratamiento especial por su número; solo la misma jerarquía aplicada de izquierda a derecha, segmento a segmento.

src/pages/blog/
  2026/resumen.astro          gana para /blog/2026/resumen
  2026/[mes].astro            atiende /blog/2026/enero
  [anio]/[mes].astro          atiende /blog/2025/marzo
  [...ruta].astro             recoge cualquier otra profundidad

En esa carpeta conviven cuatro niveles de especificidad. Para /blog/2026/resumen gana el fichero totalmente estático; /blog/2026/enero no tiene literal pero sí un [mes] bajo el año fijo 2026, más específico que el genérico [anio]/[mes]; y lo que no encaje en ninguno cae en el catch-all. Cuatro patrones, cero ambigüedad, porque cada URL halla sin disputa su candidata más concreta.

⚠️
Evita colisiones genuinamente ambiguas

La comparación segmento a segmento resuelve limpiamente los casos donde una ruta es claramente más específica que otra. Pero es posible construir colisiones cruzadas —una ruta con el segmento específico a la izquierda y otra con el específico a la derecha, como foo/[bar] frente a [baz]/qux para /foo/qux— donde la intuición de cuál es más específica se vuelve resbaladiza. Astro las resuelve de forma determinista, pero tú no deberías depender de recordar el desempate exacto: un diseño de rutas que provoca estas ambigüedades es frágil de leer y de mantener. Cuando notes que dos patrones se cruzan así, reorganiza para que la especificidad sea evidente, no algo que haya que deducir.

Cuando dos rutas quedan realmente empatadas en especificidad, Astro rompe el empate por orden alfabético de sus rutas. Es un último recurso determinista, no un criterio en el que apoyar tu diseño: si dos ficheros compiten y solo los separa el alfabeto, la ambigüedad ya es un olor de diseño. Y algo parecido rige con las rutas inyectadas por integraciones: entran en la misma ordenación por especificidad que las tuyas de fichero, así que una integración no te pisa una ruta más específica por el mero hecho de venir de un paquete.

Leer el orden final y depurar colisiones

Como toda la resolución ocurre por reglas deterministas sobre el conjunto de ficheros, el orden final es algo que puedes razonar, no adivinar. Al compilar, Astro conoce la tabla completa de rutas y su prioridad; cuando una URL no se comporta como esperabas, la pregunta correcta no es por qué Astro se equivoca —no lo hace— sino qué candidata es, según la regla, más específica que la que yo creía. Reproducir mentalmente el barrido de especificidad sobre las rutas que casan casi siempre revela al culpable.

El síntoma más habitual de una colisión mal pensada es un catch-all demasiado glotón. Un [...rest].astro colocado alto en el árbol casa con todo lo que cuelga de él, y si una página que creías cubierta por otro fichero desaparece, la sospecha inmediata debe ser que ese comodín la está interceptando. La cura no es luchar contra la prioridad, sino respetarla: si quieres que una URL escape del catch-all, dale una ruta más específica, que por definición ganará.

Hay una disciplina que evita casi todos estos problemas de raíz: diseñar el árbol de rutas de modo que, para cada URL imaginable, la candidata ganadora sea obvia sin necesidad de recordar los desempates finos. Si al mirar dos ficheros tienes que pararte a pensar cuál atiende una URL compartida, el diseño ya te está avisando. La prioridad de rutas es una red de seguridad determinista, no un sustituto de un mapa de direcciones que se lea de un vistazo.

📝
Los endpoints comparten el mismo plano

No solo compiten las páginas. Un endpoint —un fichero .ts o .js en src/pages que exporta funciones por método HTTP— ocupa también una ruta y participa de la misma ordenación por especificidad. Un src/pages/api/[id].ts y un src/pages/api/salud.ts conviven bajo las mismas reglas: lo estático gana a lo dinámico igual que entre páginas. Tenlo presente cuando mezcles vistas y API bajo una misma carpeta, porque la colisión puede darse entre una página y un endpoint, no solo entre dos páginas.

🎯

Lo especifico gana

Estatica vence a param nombrado, y este vence a catch all. La coincidencia mas concreta atiende la URL.

🧮

Segmento a segmento

Con varios parametros se compara posicion por posicion. Gana la ruta mas especifica en el barrido.

🧊

Prerender antes que servidor

Entre dos dinamicas iguales, la horneada en el build gana a la resuelta bajo demanda.

🔤

Empate alfabetico

Si nada mas las separa, el orden alfabetico decide. Depender de esto es senal de diseno ambiguo.

La prioridad de rutas es lo que convierte el enrutado en una función

Hay una razón profunda por la que Astro necesita estas reglas, y verla ilumina qué es de verdad un sistema de enrutado. Un enrutador promete algo muy concreto: dada una URL, te doy una página. Esa promesa es la de una función matemática —una entrada, una salida, sin ambigüedad—, y es justo lo que la aplicación entera da por sentado: cada dirección lleva a un lugar definido y solo uno. Pero el enrutado por ficheros, con sus patrones que se solapan, no es una función de forma natural: es una relación, donde una misma URL puede estar emparejada con varios ficheros a la vez. Los patrones dinámicos, que son su gran virtud, introducen por su propia generalidad la posibilidad de la colisión, y una colisión es exactamente el momento en que la relación deja de ser función: un mismo argumento con varias imágenes posibles. La prioridad de rutas es el mecanismo que totaliza esa relación y la fuerza a ser función. Impone un orden sobre las candidatas y de ese orden extrae, para cada URL, una única ganadora. No inventa la respuesta: la selecciona de entre las que ya casaban, aplicando un criterio —la especificidad— que además es el que un humano esperaría, porque codifica una intuición sensata: quien describe la URL con más precisión probablemente sabe mejor qué mostrar en ella. Que ese criterio sea determinista es lo que hace el sistema fiable: no basta con que exista una regla, hace falta que la misma URL produzca siempre la misma ganadora, en tu máquina y en producción, hoy y tras un refactor. El determinismo es lo que te permite razonar sobre tu sitio como un todo, saber qué existe y qué atiende cada cosa sin ejecutarlo. Y aquí está la lección que trasciende Astro: cualquier sistema que resuelva nombres contra patrones —un enrutador web, una tabla de despacho de métodos, un sistema de tipos con solapamiento, un firewall con reglas que se pisan— se topa con el mismo problema, la relación que debe volverse función, y lo resuelve con la misma clase de solución, un orden de prioridad determinista sobre la especificidad. Cuando entiendes la prioridad de rutas no como una lista de reglas que memorizar sino como la operación que convierte una relación ambigua en una función total, dejas de consultarla y empiezas a derivarla: cualquier caso nuevo lo resuelves preguntándote, simplemente, cuál de las candidatas describe la URL con más precisión.

⚔️ Predice quién atiende cada URL
  1. Crea en src/pages/blog/ un destacado.astro, un [slug].astro y un [...rest].astro, y comprueba qué fichero atiende /blog/destacado, /blog/otro y /blog/uno/dos/tres.
  2. Añade src/pages/[lang]/about.astro junto a src/pages/[lang]/[...slug].astro y verifica que /es/about lo gana la ruta estática del segundo segmento.
  3. Declara una entrada en redirects con el mismo nombre que una página existente y confirma que la redirección se impone por evaluarse antes.
  4. Diseña a propósito una colisión cruzada ambigua, razona por qué es un mal patrón y reorganiza las rutas para que la especificidad sea evidente sin recurrir al desempate.