Saltar al contenido
Mathias Parodi

6 min de lectura

Cómo está hecho este blog

Markdown en un repo, un índice en vez de una grilla de tarjetas, notas al margen que no son notas al pie, y ni una línea de JavaScript para desplegar un resumen. También sirve de referencia de todo lo que se puede escribir en una nota.

Todo lo que estás leyendo sale de un archivo .mdx que vive en content/notas/ dentro del mismo repositorio que el sitio. No hay base de datos, no hay CMS, no hay panel de administración. Escribo, hago commit, empujo, y el sitio se recompila y se publica solo. El repo es el CMS.

Esta nota es dos cosas a la vez: una explicación de por qué el sitio se ve así, y la referencia de todo lo que puedo usar cuando escribo. Si en el futuro me olvido de cómo se pone una nota al margen, vengo acá.

Una portada que es un índice

La decisión de diseño más grande fue no hacer una grilla de tarjetas. Las tarjetas son la respuesta por defecto de la web desde hace quince años y tienen un problema: cada una pide una imagen que no existe, y sin imagen quedan tres rectángulos grises pidiendo disculpas.

Un índice, en cambio, no necesita nada más que texto. Año en la canaleta, número de nota, título en tipografía de titulares, puntos guía que llevan la vista hasta la fecha. Es la tabla de contenidos de un libro, que es exactamente lo que un blog es.

Si el diseño se rompe cuando falta la imagen, el diseño estaba apoyado en la imagen.

La regla que ordenó el resto

El resumen que se despliega al pasar el mouse no usa JavaScript. Es un truco de CSS: el contenedor pasa de grid-template-rows: 0fr a 1fr y esa propiedad, desde hace un par de años, es animable. La idea, escrita en CSS a secas:

/* De 0fr a 1fr: el navegador interpola la altura sin que nadie
   tenga que medir nada en runtime. */
.fila-resumen {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 420ms cubic-bezier(0.22, 0.75, 0.19, 1);
}
.fila:hover .fila-resumen {
  grid-template-rows: 1fr;
}
.fila-resumen > * {
  overflow: hidden; /* imprescindible: sin esto el hijo no se recorta */
}

En el repo no está escrito así. Está en IndiceNotas.tsx como utilidades de Tailwind sobre la fila del índice — grid-rows-[0fr], group-hover:grid-rows-[1fr] y una transición sobre grid-template-rows —, más las variantes que apagan todo el efecto cuando no hay mouse o cuando el sistema pide menos movimiento. El CSS de arriba es la misma regla desarmada para que se lea de un vistazo.

La grilla de lectura

El artículo no ocupa el ancho de la pantalla. La caja de texto mide 40rem — más o menos 70 caracteres por línea, que es donde el ojo deja de perder el renglón — y a la derecha quedan 14rem vacíos a propósito. Ahí flotan las notas al margen.

Diagrama de la grilla: un bloque de texto de 40 rem a la izquierda y una canaleta punteada de 14 rem a la derecha, con una nota al margen conectada por una línea a su llamada en el texto.
La caja y la canaleta. La nota al margen no interrumpe el párrafo: lo acompaña.

Una nota al margen es mejor que una nota al pie porque no te obliga a saltar al final del artículo y volver.En pantallas angostas no hay canaleta, así que la nota se pliega y se abre tocando el numerito. La numeración la lleva un contador de CSS, así que agregar una nota en el medio renumera todo solo. Está ahí, a la altura de la línea que la invoca, y la leés o no la leés.

Todo lo que puedo escribir en una nota

Además del markdown de siempre, hay un puñado de componentes propios.

Apartes

Se escribe así:

<Aparte titulo="Para qué sirve un aparte">
  Una digresión que no entra en el hilo principal.
</Aparte>

Tablas

Las tablas usan monoespaciada y cifras tabulares, así que las columnas de números alinean solas:

ComponentePara quéJavaScript en el navegador
<Figura>Imagen con epígrafe y reserva de espacioNada
<Cita>Frase destacada fuera del cuerpoNada
<Aparte>Digresión con fileteNada
<Video>Video propio con controlesNada
<Nota>Nota al margen numeradaSí, es componente cliente
<YouTube>Fachada de videoSí, el mismo módulo cliente

Las cuatro primeras se arman enteras en el servidor y no mandan una línea al navegador. Las dos últimas viven en el mismo archivo con "use client", así que una nota que use cualquiera de las dos se lleva el módulo completo. En <YouTube> no hay vuelta: alguien tiene que escuchar el click que cambia la miniatura por el reproductor. En <Nota> es más discutible — el plegado lo hacen un checkbox y CSS, y lo único que necesita React ahí es generar un id estable —, y está anotado como deuda.

Video

Los videos incrustados no cargan el reproductor hasta que alguien decide mirarlos. Un iframe de YouTube arrastra medio mega de JavaScript y planta cookies antes de que nadie le haya dado play, así que lo que se ve primero es la miniatura y un botón.La miniatura sí sale de un servidor de Google (i.ytimg.com) y se pide sola, apenas la nota se muestra: es un pedido a un tercero, aunque sea nada más que una imagen. Lo que espera al play es el resto — el iframe, el JavaScript del reproductor y las cookies —, y cuando llega va contra youtube-nocookie.com.

YouTubeSpec-ulation — Rich Hickey (ClojureTV). Sobre compatibilidad, versionado y por qué romper contratos es una decisión moral.

Para videos propios servidos desde public/ está <Video>, que es un <video> con controles, sin autoplay y sin sonido sorpresa:

<Video
  src="/notas/mi-nota/demo.mp4"
  poster="/notas/mi-nota/demo.jpg"
  epigrafe="Qué se ve en el video."
/>

Código

Los bloques van resaltados con Shiki, en tiempo de compilación: el navegador recibe HTML ya coloreado y no descarga ningún resaltador. Se puede poner título al bloque, numerar las líneas y marcar las importantes:

```go title="retry/backoff.go" showLineNumbers {4-5}
func espera(intento int, base, techo time.Duration) time.Duration {
	limite := techo
	// Las líneas marcadas son las que importan: recortar antes de desplazar.
	if base > 0 && intento >= 0 && intento < 63 && base <= techo>>intento {
		limite = base << intento
	}
	return time.Duration(rand.Int63n(int64(limite)))
}
```

Como Shiki emite las dos paletas en variables CSS, el mismo HTML sirve para el tema papel y para el tema noche sin volver a resaltar nada.

Lo que no tiene

Vale la pena decir qué decidí dejar afuera, porque son decisiones tanto como las otras:

  • Comentarios. El correo está en el pie.
  • Analítica. No me interesa saber cuántos entraron.
  • Buscador. Con menos de cien notas, Ctrl+F sobre el archivo alcanza.
  • Fuentes externas en tiempo de ejecución. Las tipografías las sirve el mismo dominio. El único pedido a un tercero en todo el sitio es la miniatura de una fachada de video, que sale de i.ytimg.com en cuanto la nota se muestra, sin que nadie haya tocado play. El reproductor y las cookies sí esperan al play. Es el único lugar donde el sitio no se basta a sí mismo, y está acá escrito para no olvidármelo.

El despliegue

Un workflow de GitHub Actions corre en cada push a main: instala, compila y publica en Vercel. Lo mismo para las ramas, con una vista previa por pull request. La parte que me importa es que el paso de compilación falla si una nota tiene el frontmatter mal — sin título, sin resumen o con una fecha que no es una fecha — así que no se puede publicar algo roto sin enterarse.

.github/workflows/deploy.yml
- name: Verificar el frontmatter de las notas
  run: npm run verificar-notas
 
- name: Compilar
  run: npm run build

Es poca ceremonia para lo que hace. Que es, en el fondo, lo único que le pido a un blog: que escribir sea la parte difícil.