El routing por archivos: del fichero a la ruta
SolidStart convierte el sistema de archivos en la configuración del router: recorre la carpeta routes, recoge cada fichero con export por defecto y genera un árbol de rutas que FileRoutes entrega a solid-router. Como la URL es el reflejo directo de la ruta del fichero, la estructura de carpetas ES el mapa de navegación, sin tabla de rutas que mantener a mano.
De un fichero a una URL
La regla fundacional es directa: la ruta de un fichero dentro de routes/, quitándole la extensión, es su URL. Un fichero en la raíz de la carpeta es un segmento de primer nivel; una subcarpeta añade un segmento más. El fichero especial index representa la ruta de la carpeta que lo contiene, sin segmento adicional. Solo se convierte en página el fichero que exporta un componente por defecto: ese export es lo que se pinta cuando la URL coincide.
// src/routes/index.tsx -> /export default function Inicio() { return <h1>Bienvenido</h1>;}La correspondencia es puramente estructural, sin ceremonia intermedia:
Fichero en routes/ |
URL resultante |
|---|---|
index.tsx |
/ |
blog.tsx |
/blog |
blog/index.tsx |
/blog |
blog/solid.tsx |
/blog/solid |
equipo/ana.tsx |
/equipo/ana |
Cuentan como rutas los ficheros con extensión de componente —.tsx, .jsx— y también los de contenido —.md, .mdx—, de modo que puedes mezclar páginas escritas en código con páginas escritas en Markdown en el mismo árbol. Una carpeta sin index no responde a su propia URL desnuda: equipo/, sin un equipo/index.tsx ni un equipo.tsx hermano, deja /equipo sin página aunque sus hijos sigan siendo accesibles. Fichero, ubicación y export por defecto son las tres condiciones; si falla una, no hay ruta.
// src/routes/equipo/ana.tsx -> /equipo/anaexport default function Ana() { return <h1>Ana Ramírez</h1>;}FileRoutes: el puente entre el disco y el router
FileRoutes vive en @solidjs/start/router y no es magia opaca: devuelve un objeto de configuración de rutas, exactamente el mismo que escribirías a mano con <Route>. Por eso es agnóstico del router y encaja de forma natural dentro de @solidjs/router. Lo colocas una vez, en la raíz de la aplicación, envuelto en un <Router>. La prop root define el layout que envuelve toda la app, y sus hijos deben ir dentro de un <Suspense> porque cada página se carga de forma diferida.
// src/app.tsximport { Suspense } from "solid-js";import { Router } from "@solidjs/router";import { FileRoutes } from "@solidjs/start/router";export default function App() { return ( <Router root={(props) => <Suspense>{props.children}</Suspense>}> <FileRoutes /> </Router> );}Sin ese <Suspense> verías errores de hidratación al navegar, porque el router intentaría montar un componente que aún se está descargando. El root es tu sitio para la cáscara global: cabecera, pie, proveedores de contexto, todo lo que debe sobrevivir a cualquier navegación.
Que FileRoutes devuelva datos y no comportamiento tiene una consecuencia liberadora: no quedas encerrado en la convención. Como es una configuración de rutas normal, puedes intercalar rutas manuales junto a las generadas, envolverlas o repartirlas entre routers distintos si tu aplicación lo pidiera.
// FileRoutes es una config: se compone con rutas manualesimport { Router, Route } from "@solidjs/router";import { FileRoutes } from "@solidjs/start/router";export default function App() { return ( <Router root={Layout}> <FileRoutes /> <Route path="/salud" component={() => <span>ok</span>} /> </Router> );}En qué se expande la magia
Conviene desmitificar FileRoutes viendo su equivalente manual. En tiempo de compilación, un plugin recorre la carpeta y sustituye FileRoutes por un objeto de rutas donde cada página se envuelve en lazy para que su código viaje en un fragmento aparte. Escribir esto a mano es justo lo que la convención te ahorra:
// Equivalente aproximado de lo que FileRoutes genera:import { Router, Route } from "@solidjs/router";import { lazy } from "solid-js";<Router root={Layout}> <Route path="/" component={lazy(() => import("./routes/index"))} /> <Route path="/blog" component={lazy(() => import("./routes/blog"))} /></Router>;Ese lazy por página es la razón del <Suspense> del root: cada ruta es un punto de división de código, y su descarga es asíncrona. La ventaja arquitectónica es doble. Primero, el usuario solo baja el JavaScript de la página que visita, no el de toda la app. Segundo, la tabla de rutas nunca puede desincronizarse de la realidad, porque es la realidad del disco: no hay dos fuentes que puedan divergir.
Hay una tercera ventaja, menos visible pero decisiva en proyectos grandes: la localidad. Como cada ruta es un fichero autónomo con su propio punto de entrada, razonas sobre una página sin cargar en la cabeza el mapa entero; abrir el fichero es abrir la ruta. Y como el bundler ve cada página como un módulo diferido independiente, el grafo de dependencias se parte de forma natural por rutas, que es justo el eje por el que un usuario navega. La arquitectura del código y la de la navegación dejan de ser dos diagramas que mantener en sincronía y pasan a ser el mismo diagrama.