Rutas dinámicas y catch-all: corchetes como gramática
Los corchetes en el nombre de un fichero son un pequeño lenguaje: un par simple id captura un segmento dinámico, doble corchete id lo hace opcional, y corchetes con puntos slug capturan el resto de la ruta entera. Los paréntesis, en cambio, agrupan ficheros sin tocar la URL. Cada forma se traduce a una capacidad del router: parámetro, opcional, comodín o grupo sin ruta, y matchFilters valida que un parámetro tenga el formato correcto antes de coincidir.
Segmentos dinámicos con corchetes
Un par de corchetes en el nombre convierte ese segmento en un parámetro: routes/usuarios/[id].tsx responde a /usuarios/1, /usuarios/2 y cualquier otro valor, capturándolo bajo el nombre id. Dentro del componente lo lees con useParams, que devuelve un objeto reactivo. Puedes encadenar varios: usuarios/[id]/[pestana].tsx captura dos.
// src/routes/usuarios/[id].tsx -> /usuarios/:idimport { useParams } from "@solidjs/router";export default function Usuario() { const params = useParams(); return <h1>Usuario {params.id}</h1>;}Dos propiedades merecen destacarse. La primera es que una ruta literal siempre gana a una dinámica: si colocas usuarios/nuevo.tsx junto a usuarios/[id].tsx, la URL /usuarios/nuevo sirve la página literal, no la dinámica con id valiendo nuevo. Lo concreto vence a lo genérico, sin que ajustes prioridad alguna. La segunda es que, al navegar entre dos valores del mismo parámetro —de /usuarios/1 a /usuarios/2—, el componente no se remonta: es la misma plantilla de ruta, así que solo se recalcula lo que depende de params.id. Y nada te limita a un parámetro por ruta: usuarios/[id]/entradas/[entradaId].tsx captura dos segmentos dinámicos separados por uno fijo, y useParams te devuelve ambos en el mismo objeto.
Catch-all: absorber el resto de la ruta
Un catch-all antepone ... al nombre dentro de los corchetes —[...slug]— y captura cualquier número de segmentos restantes en un único parámetro, unidos por barras. Con routes/docs/[...ruta].tsx, una URL /docs/guia/router deja params.ruta valiendo guia/router. Es la herramienta para documentación jerárquica, navegadores de archivos y, sobre todo, para la página 404: un fichero routes/[...404].tsx en la raíz atrapa todo lo que ninguna otra ruta reclamó.
// src/routes/docs/[...ruta].tsx -> captura /docs/lo/que/seaimport { useParams } from "@solidjs/router";export default function Docs() { const params = useParams(); // /docs/guia/router -> params.ruta === "guia/router" const partes = () => params.ruta.split("/"); return <article>Profundidad: {partes().length}</article>;}El catch-all es la red de seguridad del enrutado: como coincide con cualquier cosa, tiene la prioridad más baja, de modo que solo captura lo que ninguna ruta más específica reclamó. Colocado en la raíz como routes/[...404].tsx, se convierte en tu página de “no encontrado” sin configuración adicional; dentro de una carpeta, atrapa las profundidades de esa sección. Conceptualmente va siempre al final de su nivel: nada más específico puede vivir después de algo que lo absorbe todo.
// src/routes/[...404].tsx -> atrapa cualquier url no reclamadaimport { HttpStatusCode } from "@solidjs/start";export default function NoEncontrado() { return ( <main> <HttpStatusCode code={404} /> <h1>Página no encontrada</h1> </main> );}Opcionales con doble corchete
El doble corchete [[id]] hace el parámetro opcional: la ruta coincide tanto si el segmento está como si no. Un routes/usuarios/[[id]].tsx sirve a la vez /usuarios y /usuarios/1. Dentro, params.id estará definido o no según el caso, así que lo tratas con una comprobación. Es el patrón para vistas que muestran un listado cuando no hay selección y un detalle cuando lo hay, sin duplicar la ruta.
// src/routes/usuarios/[[id]].tsx -> /usuarios y /usuarios/1import { Show } from "solid-js";import { useParams } from "@solidjs/router";export default function Usuarios() { const params = useParams(); return ( <Show when={params.id} fallback={<ListaUsuarios />}> <FichaUsuario id={params.id} /> </Show> );}Conviene conocer su límite: la opcionalidad afecta solo al último segmento. Un parámetro opcional coincide con y sin ese tramo final, pero no habilita rutas más profundas tras él; a nivel de router equivale a un :id?, que cubre /usuarios y /usuarios/1 pero no /usuarios/1/editar. Para lo demás combinas piezas: un opcional para el último tramo, dinámicos para los intermedios, un catch-all para lo verdaderamente abierto.
Grupos con paréntesis: organizar sin tocar la URL
Los paréntesis hacen algo distinto de los corchetes: agrupan sin afectar la ruta. Una carpeta routes/(marketing)/ no aporta ningún segmento, de modo que routes/(marketing)/sobre/index.tsx sirve /sobre, no /marketing/sobre. Sirve para dos cosas: ordenar ficheros por área cuando el sistema de carpetas por sí solo no lo permite, y —combinado con un layout homónimo— aplicar una envoltura común a un conjunto de rutas sin meter un segmento artificial en la URL. La misma idea permite escapar de un layout: nombrar una carpeta como usuarios(detalle)/[id].tsx produce /usuarios/1 pero con un layout propio, fuera del de usuarios.
routes/ (marketing).tsx <- layout comun, sin segmento (marketing)/ sobre.tsx <- /sobre precios.tsx <- /precios usuarios.tsx <- layout de /usuarios usuarios/ index.tsx <- /usuarios usuarios(detalle)/ [id].tsx <- /usuarios/1, con layout propioAquí (marketing).tsx envuelve /sobre y /precios con una cáscara común sin que la palabra marketing aparezca jamás en la barra de direcciones, mientras que usuarios(detalle)/ produce URLs bajo /usuarios pero deliberadamente fuera del layout de usuarios.tsx. Paréntesis para agrupar y compartir; paréntesis con nombre para escapar de la envoltura vecina.