wandres.dev
FILE ROUTING · rutas y layouts

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.

⏱ 15 min

En SolidStart no existe una tabla central de rutas que edites a mano: la carpeta routes/ es esa tabla. Cada fichero que colocas dentro se convierte en una URL, y su ruta relativa dentro de routes/ dicta el camino que el usuario teclea en la barra de direcciones. El compilador recorre ese árbol de ficheros, recoge los que exportan un componente por defecto y construye con ellos una configuración de rutas que el componente FileRoutes inyecta en @solidjs/router. El disco deja de ser almacenamiento y pasa a ser la fuente de la verdad de tu navegación.

🎯 Al terminar esta lección sabrás
  • Entender que la carpeta routes/ es la configuración del router, no una convención cosmética.
  • Traducir a ojo una ruta de fichero a su URL, y una URL a su fichero.
  • Montar Router con su prop root y colocar FileRoutes para generar las rutas.
  • Ver en qué se expande FileRoutes y por qué cada página se carga de forma diferida.

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/ana
export 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.tsx
import { 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 manuales
import { 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>
  );
}
ℹ️
Solo cuenta el export por defecto

En routes/ conviven las páginas de UI y las rutas de API. FileRoutes recoge únicamente las primeras: un fichero es página si exporta un componente por defecto. Una ruta de API, en cambio, exporta funciones nombradas por verbo HTTP —GET, POST— y FileRoutes la ignora. Un fichero sin export por defecto ni verbos no genera ninguna ruta; te sirve como módulo auxiliar colocado junto a lo que usa.

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.

flowchart LR
FS[carpeta routes en disco] -->|compilador recorre| COL[recoleccion de ficheros]
COL -->|export por defecto| CFG[objeto de configuracion de rutas]
CFG -->|FileRoutes inyecta| R[solid router]
R -->|coincide la url| P[pagina con lazy y suspense]
style CFG fill:#89b4fa,color:#11111b
style P fill:#a6e3a1,color:#11111b
📄

Un fichero, una ruta

Coloca sobre.tsx en routes/ y ya tienes /sobre. Nada más que crear el fichero y exportar un componente por defecto.

🗂️

Carpetas anidan

Una subcarpeta añade un segmento. docs/api.tsx es /docs/api; el index de una carpeta es su propia ruta.

🔌

Router agnóstico

FileRoutes devuelve una config, no un router. Se la das a @solidjs/router porque encaja, pero es un objeto de datos.

✂️

División automática

Cada página se envuelve en lazy. El usuario descarga solo la ruta que visita, sin que tú toques la configuración del bundler.

💡
Coloca junto a la ruta lo que la ruta usa

Como cada ruta es un fichero en una carpeta, esa carpeta es el hogar natural de todo lo que la ruta necesita: un componente auxiliar que solo ella usa, sus estilos, su función de carga de datos. Colocarlos al lado —en vez de en un lejano directorio de “componentes”— convierte la estructura del proyecto en un mapa de funcionalidades, no de tipos de fichero. El routing por archivos no solo ordena las URLs: empuja, con suavidad, hacia una organización por dominio.

El sistema de archivos ES la configuración del router

La idea que gobierna todo este nivel es una inversión de responsabilidad: dejas de describir tus rutas y pasas a materializarlas como estructura de carpetas. En un router clásico mantienes una tabla —un array de objetos con path y component— que es un modelo del árbol de tu aplicación, y ese modelo puede mentir: un fichero movido, un componente renombrado, una ruta olvidada, y la tabla y el disco divergen sin que nadie se entere hasta que algo rompe en producción. El routing por archivos elimina de raíz esa clase de bug porque suprime la duplicación: no hay modelo y realidad, hay una sola cosa que es a la vez ambas. La URL se convierte en una proyección mecánica del árbol de ficheros, tan predecible que puedes navegar el código y la aplicación con el mismo mapa mental. Y como FileRoutes no es más que un generador que traduce ese árbol a la misma configuración que escribirías a mano, no pierdes nada de potencia: sigues teniendo <Suspense>, lazy, layouts y precarga, solo que expresados como convención en vez de como código repetitivo. Esta es la diferencia entre configurar y convenir: convenir escala, porque cada fichero que creas ya está, por su mera existencia y ubicación, correctamente enrutado. La colocación —tener la página, sus estilos y sus datos en la misma carpeta— deja de ser una disciplina que te impones y pasa a ser la forma natural del proyecto.

⚔️ Materializa tu primer árbol de rutas
  1. Crea routes/index.tsx y routes/sobre.tsx, cada uno con un componente por defecto, y comprueba que responden en / y /sobre.
  2. Añade routes/blog/index.tsx y routes/blog/primero.tsx; verifica que sirven /blog y /blog/primero respectivamente.
  3. Monta app.tsx con <Router>, la prop root y FileRoutes; quita el <Suspense> del root y observa el error de hidratación al navegar.
  4. Añade un fichero en routes/ sin export por defecto y confirma que no genera ninguna ruta accesible.
  5. Escribe a mano la configuración equivalente con <Route> y lazy para dos de tus páginas, y contrasta el ruido de hacerlo a mano con la convención.