Páginas, navegación y estructura de un proyecto
Los tres capítulos anteriores trabajaron sobre un mismo terreno: una página. En el capítulo 2 levantaste un proyecto con una sola entrada y moviste un deslizador; en el capítulo 3 entendiste cómo el grafo reactivo reordena la ejecución de los bloques dentro de ese archivo. Ahora conviene subir un nivel y mirar el sitio completo: varias páginas ordenadas, una barra lateral que las agrupa, un encabezado común, rutas limpias y archivos compartidos entre documentos.
Este capítulo explica esa capa. No es una capa meramente cosmética: la estructura de carpetas de un proyecto en Observable Framework determina la estructura de URL del sitio final, y el archivo observablehq.config.js decide qué se ve, en qué orden y bajo qué jerarquía. Saber leer ambos es la diferencia entre un proyecto que crece con orden y uno que se vuelve un laberinto a los diez archivos.
En Observable Framework, la estructura del sitio es la estructura de carpetas dentro de src/. El observablehq.config.js no dicta el enrutamiento; solo describe cómo presentar al lector lo que ya existe en el sistema de archivos.
Anatomía de un proyecto
Un proyecto recién creado tiene pocas piezas, pero cada una ocupa un lugar preciso (Observable, Inc., 2026). La Figura 4.1 muestra el árbol típico y el rol de cada directorio.
.md dentro de src/, datos en src/data/ y módulos compartidos en src/components/. En gris, lo que genera el framework: la caché de .observablehq/ y el dist/ que produce npm run build.Tres detalles merecen señalarse. El primero: el archivo observablehq.config.js vive fuera de src/, en la raíz del proyecto, y por eso no se publica como página. El segundo: la ruta src/ es solo el valor por omisión de la opción root; puede cambiarse, pero el resto del libro, por convención, asume src (Observable, Inc., 2026). El tercero: la carpeta .observablehq/cache/ contiene los archivos que el framework guarda para no volver a ejecutar data loaders sin cambios, y está destinada a permanecer sin tocar por la persona autora. Borrarla a mano es legítimo (lo hace npm run clean), editarla no.
En este libro, las páginas están organizadas en cinco carpetas bajo src/chapters/, una por parte. Cada capítulo es un .md con un nombre numerado que preserva el orden de lectura incluso cuando el sistema operativo no lo respeta.
Enrutamiento basado en archivos
La regla es tan simple como productiva: cada archivo Markdown dentro de src/ se convierte, al construir el sitio, en una página HTML cuya URL coincide con su ruta relativa, sin la extensión (Observable, Inc., 2026). No hay tabla de enrutamiento intermedia, ni decoradores, ni registro manual.
La Tabla 4.1 resume cómo un mismo archivo se traduce en URL según las opciones cleanUrls y preserveIndex del observablehq.config.js.
Archivo en src/ |
URL por omisión | Con preserveExtension: true |
Con preserveIndex: true |
|---|---|---|---|
index.md |
/ |
/index.html |
/index |
acerca.md |
/acerca |
/acerca.html |
/acerca |
chapters/intro.md |
/chapters/intro |
/chapters/intro.html |
/chapters/intro |
chapters/index.md |
/chapters/ |
/chapters/index.html |
/chapters/index |
.html al final y con la barra diagonal como sustituto de index. Las opciones preserveExtension y preserveIndex revierten cada una de esas comodidades cuando el servidor de despliegue lo exige.La página que estás leyendo vive en src/chapters/parte-1-fundamentos/04-paginas-navegacion-estructura.md y se sirve en /chapters/parte-1-fundamentos/04-paginas-navegacion-estructura. No hay magia: la URL reproduce la carpeta. Eso tiene dos consecuencias prácticas. La primera: los enlaces internos entre capítulos usan rutas relativas del mismo sistema de archivos (./03-markdown-reactivo, ../parte-2-datos/05-data-loaders). La segunda: mover un archivo cambia su URL, por lo que reorganizar la carpeta a mitad del proyecto puede romper enlaces externos que ya hayan sido publicados.
Antes de renombrar o mover un capítulo ya publicado, considera si conviene dejar un redireccionador. Observable Framework no ofrece redirecciones integradas; si el sitio se despliega en Netlify, Cloudflare Pages o GitHub Pages, la redirección se configura del lado del servidor. Elegir nombres de archivo pensando a largo plazo evita ese trámite.
Front matter: la página habla por sí misma
El front matter es un bloque YAML opcional que va al inicio del archivo, delimitado por dos líneas con tres guiones (---). El framework lo lee antes de procesar el cuerpo Markdown y usa sus claves como metadatos de esa página: título, tema, si aparece en el buscador, si se excluye del build, etc. Es, en la práctica, la forma más directa de ajustar una página sin tocar la configuración global.
Un ejemplo típico al principio de un .md:
---
title: "Informe trimestral"
toc: false
draft: true
style: ../estilos/informe.css
index: false
---
Cada .md puede abrir con un bloque como el anterior. Las claves declaradas ahí afectan únicamente a esa página (Observable, Inc., 2026) y, cuando coinciden con una opción del observablehq.config.js, prevalecen sobre el valor global. La Tabla 4.2 reúne las opciones de uso más frecuente.
| Clave | Tipo | Default | Para qué sirve |
|---|---|---|---|
title |
texto | nombre del archivo | Encabezado de la página y etiqueta en pestañas del navegador. |
toc |
booleano | true |
Mostrar u ocultar la tabla de contenidos lateral derecha. |
draft |
booleano | false |
Excluir la página del build sin borrarla. |
index |
booleano | true |
Incluir o no la página en el buscador integrado. |
sidebar |
booleano | hereda del config | Mostrar u ocultar la barra lateral para esa página. |
style |
ruta | — | Archivo CSS con ámbito limitado a esa página. |
theme |
texto o lista | hereda del config | Tema visual, solo o compuesto (p. ej. ["air", "near-midnight"]). |
head |
HTML | — | Etiquetas adicionales dentro del <head> de esa página. |
header, footer |
HTML | hereda del config | Reemplazan el encabezado o pie global en esa página. |
sql |
objeto | — | Registra tablas accesibles con bloques sql en la página. |
observablehq.config.js.El front matter es la herramienta adecuada cuando una página tiene una necesidad particular que no comparte con el resto. Una portada con cabecera distinta, un borrador que todavía no debe publicarse, un capítulo con un tema oscuro para resaltar gráficos en mapas: casos puntuales. Para decisiones que afectan a todo el sitio, el lugar correcto es el observablehq.config.js.
El observablehq.config.js como plano general
El archivo de configuración es un módulo JavaScript que exporta por omisión un objeto. Ese objeto describe el sitio: su título, las opciones globales de navegación, el tema, los intérpretes de data loaders y, sobre todo, la jerarquía de páginas para la barra lateral (Observable, Inc., 2026). La Tabla 4.3 recoge las opciones que conviene conocer desde el primer proyecto.
| Opción | Tipo | Default | Alcance |
|---|---|---|---|
title |
texto | — | Título global del sitio. |
root |
ruta | "src" |
Carpeta que el framework trata como raíz. |
output |
ruta | "dist" |
Carpeta de salida del build. |
base |
texto | "" |
Prefijo de URL cuando el sitio se sirve en un subdirectorio. |
pages |
lista | — | Jerarquía explícita de la barra lateral. |
sidebar |
booleano | true |
Mostrar u ocultar la barra lateral por defecto. |
toc |
booleano | true |
Mostrar la tabla de contenidos por defecto. |
pager |
booleano | true |
Enlaces de página anterior y siguiente al pie. |
search |
booleano u objeto | true |
Habilitar el buscador integrado. |
cleanUrls |
booleano | true |
Servir rutas sin .html. |
theme |
texto o lista | "default" |
Tema global o composición de temas. |
head, header, footer |
HTML o función | — | Contenido común en <head>, encabezado y pie. |
style |
ruta | — | Hoja de estilos global. |
interpreters |
objeto | — | Intérpretes para extensiones de data loaders. |
markdownIt |
función | — | Extender el motor de Markdown con plugins. |
dynamicPaths |
lista o generador | — | Rutas adicionales para páginas paramétricas. |
observablehq.config.js más usadas. Hay otras menos frecuentes (typographer, linkify, duckdb, sql global) que aparecen cuando el proyecto las necesita.El servidor de desarrollo (npm run dev) recarga en caliente los .md y los módulos importados, pero no el observablehq.config.js. Cada cambio en ese archivo exige detener el proceso y volver a lanzarlo. Si un ajuste parece no surtir efecto, lo primero que hay que comprobar es justamente eso.
Este libro ilustra la mayoría de esas opciones en un mismo archivo: define title, declara la jerarquía completa de capítulos en pages, sustituye el encabezado con una marca propia de Social Data Ibero, añade un pie de licencia, inyecta metadatos en head, reemplaza el tema por un style personalizado y carga el plugin markdown-it-biblatex para las citas bibliográficas. El patrón es deliberado: un solo archivo reúne todas las decisiones que afectan al sitio entero.
Diseñar la barra lateral
La opción pages es, en proyectos de más de tres o cuatro documentos, la que más tiempo ocupa durante la configuración. Si se omite, el framework genera automáticamente la barra lateral en orden alfabético, lo cual suele bastar para prototipos pero rara vez para un libro. Declararla de forma explícita permite agrupar páginas en secciones, decidir el orden de lectura y controlar qué aparece expandido al cargar el sitio.
Cada sección es un objeto con tres campos: name (texto visible), pages (lista de páginas hijas) y open (si la sección arranca desplegada). Cada página, a su vez, lleva name y path. La Figura 4.2 muestra el fragmento del observablehq.config.js que construye la Parte I de este libro.
pages: [
{
name: "Parte I — Fundamentos",
open: false,
pages: [
{ name: "Cap. 1. ¿Qué es Observable Framework?", path: "/chapters/parte-1-fundamentos/01-que-es-observable-framework" },
{ name: "Cap. 2. Tu primer proyecto en 20 minutos", path: "/chapters/parte-1-fundamentos/02-primer-proyecto" },
{ name: "Cap. 3. Markdown reactivo", path: "/chapters/parte-1-fundamentos/03-markdown-reactivo" },
{ name: "Cap. 4. Páginas, navegación y estructura", path: "/chapters/parte-1-fundamentos/04-paginas-navegacion-estructura" }
]
},
// …Parte II, III, IV y V…
]
observablehq.config.js del libro. La lista pages replica la jerarquía que ves en la barra lateral a la izquierda. El campo path no incluye la extensión porque el framework sirve URL limpias por omisión.Las secciones pueden anidarse a cualquier profundidad, pero rara vez es buena idea pasar de dos niveles: una barra lateral con tres niveles de jerarquía obliga al lector a mantener un mapa mental que pocos proyectos justifican. El precedente empírico en arquitectura de la información sugiere jerarquías anchas antes que profundas, y pocas etiquetas por nivel, para que el ojo pueda recorrer la estructura sin esfuerzo (Morville et al., 2015).
pages y cuándo no
Para un tablero con dos o tres páginas hermanas, el orden alfabético automático es suficiente y evita mantener una lista que se desincroniza con la carpeta. En un libro, un informe por capítulos o un sitio con secciones distintas, la declaración explícita vale la pena: el orden pedagógico no suele coincidir con el orden alfabético, y los nombres visibles (con acentos, prefijos romanos, números) rara vez son iguales al nombre del archivo.
Dos detalles finales sobre la navegación. El pager añade al pie de cada página dos enlaces, «anterior» y «siguiente», que siguen el orden declarado en pages; si prefieres desactivarlo por completo, basta con pager: false en el config. El buscador integrado (search: true por omisión) indexa todo el contenido textual del sitio y permite excluir páginas concretas con index: false en su front matter, por ejemplo cuando una página contiene datos confidenciales o tiene un propósito de diagnóstico interno.
Rutas paramétricas: una plantilla, muchas páginas
Hasta aquí cada página era un archivo. Cuando un proyecto necesita generar decenas o cientos de páginas con la misma estructura y datos distintos (un perfil por usuario, una ficha por producto, un informe por entidad federativa), duplicar archivos deja de tener sentido. Para estos casos, el framework ofrece rutas paramétricas: un solo archivo cuyo nombre lleva una parte entre corchetes, que se materializa en varias URL al construir el sitio (Observable, Inc., 2026).
El archivo src/estados/[estado].md representa una plantilla donde estado es un parámetro. Al hacer el build, el framework consulta la opción dynamicPaths del config (o el propio dynamicPaths declarado en el front matter como generador asíncrono) para saber qué valores debe generar. La Figura 4.3 ilustra el flujo.
.md se renderiza 32 veces con el valor del parámetro sustituido.Dentro de la plantilla, el valor del parámetro se lee como observable.params.estado, tanto en bloques de JavaScript como en expresiones en línea dentro de la prosa. Hay una particularidad que conviene retener: FileAttachment exige en general una ruta literal (una cadena fija, sin interpolación), porque el framework analiza el texto de forma estática para decidir qué archivos empaquetar. Las rutas paramétricas son la única excepción reconocida, precisamente porque dynamicPaths comunica al framework la lista cerrada de valores posibles (Observable, Inc., 2026). Así, dentro de una plantilla [estado].md esta invocación sí es válida:
FileAttachment(`datos/${observable.params.estado}.json`)
Esta combinación de plantillas más generador resuelve con un puñado de líneas lo que en otros sistemas requiere scripts de generación de sitios, y lo hace preservando la reactividad: cada página generada es tan interactiva como una escrita a mano.
Compartir datos y código entre páginas
A medida que crece el proyecto, dos preguntas aparecen: ¿dónde viven los datos que varias páginas consumen? ¿Dónde el código que varias páginas reutilizan? La respuesta canónica de Observable Framework se apoya en dos convenciones.
Los datos compartidos viven en src/data/. Dentro de una página, FileAttachment resuelve las rutas de forma relativa al archivo .md que la invoca (Observable, Inc., 2026). Desde una página dos niveles adentro (src/chapters/parte-2-datos/ejemplo.md), acceder a un archivo de datos común se hace con FileAttachment("../../data/paises.csv"). Si la misma página se moviera a otra carpeta, la ruta relativa cambiaría: por eso, en proyectos grandes, suele ser cómodo centralizar la lectura en un módulo dentro de src/components/ que exponga los datos ya cargados y reexpuestos, de modo que cada página importe el módulo en lugar de calcular rutas relativas.
El código compartido (funciones de utilidad, componentes de visualización, helpers) vive en src/components/. Los imports siguen las reglas normales de JavaScript: rutas relativas con literal de cadena, como import {tarjeta} from "../../components/tarjeta.js" (Observable, Inc., 2026). Al construir el sitio, el framework reescribe cada import a una URL con hash dentro de dist/_import/, lo que habilita el cacheado agresivo por parte del navegador sin comprometer la actualización cuando el módulo cambia.
Esta separación (páginas en carpetas de contenido, datos en data/, módulos en components/) no es obligatoria pero funciona bien en la práctica. La razón es que refleja el recorrido natural del lector (páginas) y el recorrido natural del mantenimiento (datos y componentes), sin mezclar ambos. Garrett describe esa separación como parte del «plano de estructura» en el diseño de productos digitales: decidir qué es contenido y qué es armazón, antes de decidir cómo se ven (Garrett, 2011).
Jerarquía de la información antes que jerarquía de archivos
Llegados a este punto, quizá la tentación sea abrir el observablehq.config.js y empezar a escribir secciones y subsecciones. Antes conviene detenerse en tres preguntas, ninguna técnica, cuya respuesta orienta la arquitectura del sitio.
Audiencia. ¿Quién leerá el sitio? La barra lateral de un libro para estudiantes se organiza por orden pedagógico; la de un tablero para analistas, por frecuencia de consulta; la de un informe público, por temas. Una misma estructura de datos admite estructuras de navegación muy distintas.
Ruta primaria. ¿Cuál es la página que el 80 % de los lectores abrirá primero? Suele ser index.md. Si no lo es, conviene pensar si esa página existente debería trasladarse a la raíz.
Profundidad aceptable. ¿Cuántos clics de distancia puede estar la página más profunda? Más de tres niveles en la barra lateral es, casi siempre, señal de que hay que reagrupar.
Estas preguntas no tienen respuestas únicas; las respuestas cambian con el proyecto, la audiencia y el momento del ciclo de desarrollo. Lo que no cambia es que el observablehq.config.js es mejor redactarlo cuando ya se han respondido que mientras se contestan.
Cierre y puente a la Parte II
Tres ideas resumen el capítulo. Primera: la estructura del sitio es la estructura de carpetas; el enrutamiento no se configura, se deduce. Segunda: el front matter decide lo específico de una página y el observablehq.config.js decide lo común a todas; la disciplina está en no duplicar información entre ambos. Tercera: la navegación (barra lateral, pager, buscador) es una superficie sobre una arquitectura de información, y merece pensarse antes de codificarse.
El enrutamiento, la construcción de la barra lateral, la resolución de rutas de archivos y la reescritura de imports ocurren sin intervención. Tu tarea se reduce a decidir dónde vive cada archivo y qué quieres que el lector vea; el framework se encarga del resto.
Resumen: dónde configurar qué
Si tuviéramos que condensar el capítulo en una sola pregunta operativa, sería esta: ¿lo que quieres ajustar afecta a una sola página o al sitio entero? Lo primero va en el front matter del .md; lo segundo, en el observablehq.config.js. Algunas claves aceptan ambos lugares y, cuando coinciden, la página siempre gana sobre el valor global.
La Tabla 4.4 reúne las opciones más útiles de este capítulo junto con su lugar natural de definición. Sirve como referencia rápida al empezar un proyecto nuevo o al revisar uno ajeno.
| Opción | Qué hace | Dónde se modifica |
|---|---|---|
title |
Título visible de la página o del sitio | Ambos |
toc |
Tabla de contenidos lateral derecha | Ambos |
sidebar |
Muestra u oculta la barra lateral | Ambos |
theme, style |
Tema visual u hoja de estilos personalizada | Ambos |
head, header, footer |
Contenido en <head>, encabezado y pie |
Ambos |
draft |
Excluye la página del build sin borrarla | Front matter |
index |
Incluye o excluye la página del buscador | Front matter |
sql |
Registra tablas accesibles con bloques sql |
Front matter |
pages |
Jerarquía explícita de la barra lateral | observablehq.config.js |
pager, search, cleanUrls |
Paginador, buscador y URL limpias | observablehq.config.js |
root, output, base |
Carpeta raíz, carpeta de salida y prefijo de URL | observablehq.config.js |
interpreters |
Intérpretes para extensiones de data loaders | observablehq.config.js |
dynamicPaths |
Lista de valores para rutas paramétricas | Ambos (generador en la página o lista global) |
markdownIt |
Plugins del motor Markdown (p. ej. citas) | observablehq.config.js |
observablehq.config.js exige reiniciar npm run dev.Con esto cierra la Parte I. Ya sabes qué es Observable Framework, cómo levantar un proyecto, cómo funciona la reactividad dentro de una página y cómo organizar muchas páginas en un sitio coherente. El siguiente capítulo, Data loaders, abre la Parte II y entra a la cocina: cómo alimentar ese sitio con datos reales, en el lenguaje que prefieras, con caché automática y sin sacrificar la reactividad.