Rutas anidadas y layouts: envolver con props.children
Las carpetas anidan segmentos de URL, y un fichero hermano con el mismo nombre que una carpeta se convierte en su layout: un componente que envuelve a todas las rutas hijas y pinta el hijo activo con props.children. Las rutas index sirven la ruta de la carpeta en sí, y los layouts persisten entre navegaciones hermanas sin remontarse, conservando estado y DOM. Aquí se ve el detalle sutil de que blog index no es lo mismo que blog layout.
Carpetas que anidan segmentos
Cada subcarpeta dentro de routes/ añade un segmento a la URL. Una carpeta blog/ con ficheros dentro produce rutas bajo /blog/..., y el fichero index de esa carpeta responde exactamente a /blog. Hasta aquí es pura estructura de directorios; lo interesante aparece cuando queremos que todas esas rutas compartan una envoltura común —una barra lateral, un encabezado de sección— sin repetirla en cada página.
routes/ blog.tsx <- layout de /blog/* blog/ index.tsx <- /blog solid.tsx <- /blog/solid astro.tsx <- /blog/astroEl fichero hermano que envuelve
Para crear un layout, escribes un fichero con el mismo nombre que la carpeta, a su lado. Ese blog.tsx no es una página más: se convierte en el envoltorio de todo lo que vive dentro de blog/. Recibe la página hija en props.children y decide dónde renderizarla. El tipo RouteSectionProps de @solidjs/router te da ese children ya tipado.
// src/routes/blog.tsximport type { RouteSectionProps } from "@solidjs/router";export default function LayoutBlog(props: RouteSectionProps) { return ( <div class="blog"> <aside> <h2>Secciones</h2> <nav>...</nav> </aside> <main>{props.children}</main> </div> );}El props.children es el hueco por donde entra la ruta activa: cuando el usuario está en /blog/solid, ahí se pinta el componente de blog/solid.tsx; cuando pasa a /blog/astro, solo cambia lo que hay en ese hueco. La <aside> permanece. Los layouts se componen en cadena: si dentro de blog/ hubiera otra carpeta con su propio layout, tendrías dos capas, cada una pintando su props.children, reflejando exactamente la jerarquía de segmentos de la URL.
La cadena se ve mejor con dos niveles: un layout de sección y, dentro, un sublayout que a su vez expone su hueco.
routes/ panel.tsx <- layout externo de /panel/* panel/ ajustes.tsx <- layout interno de /panel/ajustes/* ajustes/ perfil.tsx <- /panel/ajustes/perfil seguridad.tsx <- /panel/ajustes/seguridadEn /panel/ajustes/perfil, panel.tsx envuelve a ajustes.tsx, que envuelve a perfil.tsx, cada capa pintando la siguiente en su props.children. La anidación de carpetas se ha vuelto anidación de componentes, segmento a segmento.
// src/routes/panel/ajustes.tsx — sublayout de /panel/ajustesimport type { RouteSectionProps } from "@solidjs/router";export default function LayoutAjustes(props: RouteSectionProps) { return ( <section class="ajustes"> <nav>Perfil · Seguridad</nav> <div class="panel-ajustes">{props.children}</div> </section> );}Rutas index y un matiz que confunde
Aquí está el detalle que atrapa a casi todo el mundo: blog/index.tsx y blog.tsx no son lo mismo. El fichero index sirve una sola ruta, la de la carpeta en sí —/blog—, y es una página normal, no una envoltura. El layout es el hermano homónimo blog.tsx, y es quien envuelve a todas las rutas de la carpeta, incluida el propio index. Quien crea blog/index.tsx esperando que envuelva a los hijos descubre que no pasa nada: el index no ve a sus vecinos, solo se pinta a sí mismo en /blog.
Los layouts persisten
La consecuencia más valiosa de esta arquitectura es que un layout no se remonta cuando navegas entre sus hijos. Al ir de /blog/solid a /blog/astro, LayoutBlog sigue siendo la misma instancia: su estado local, su scroll, sus efectos, todo sobrevive. Solo cambia el contenido de props.children. Esto es la reactividad de grano fino aplicada al enrutado: el router no reconstruye el árbol, sustituye la hoja que cambió.
// src/routes/blog.tsximport { createSignal } from "solid-js";import type { RouteSectionProps } from "@solidjs/router";export default function LayoutBlog(props: RouteSectionProps) { // Este estado sobrevive a la navegacion entre articulos: const [abierta, setAbierta] = createSignal(true); return ( <div classList={{ blog: true, "con-panel": abierta() }}> <aside> <button onClick={() => setAbierta((v) => !v)}>Alternar panel</button> </aside> <main>{props.children}</main> </div> );}El panel que el usuario abrió no se cierra al saltar de un artículo a otro, porque el layout nunca desapareció. Comparado con un modelo que desmonta y remonta la pantalla entera en cada navegación, aquí conservas foco, animaciones en curso y peticiones que el layout tuviera vivas. La regla mental es limpia: lo que comparten dos rutas hermanas, ponlo en su layout, y persistirá gratis.
Esa persistencia se extiende a los datos. Un layout es también el lugar donde declarar la carga que sus hijos comparten: si /blog/solid y /blog/astro necesitan la lista de categorías, la pides una vez en blog.tsx y sobrevive a la navegación entre artículos sin recargarse. Los layouts exponen además un route con una función preload para calentar esos datos antes de que el hijo se monte, encadenando persistencia y precarga en el mismo nivel del árbol.