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.
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.
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:
| Componente | Para qué | JavaScript en el navegador |
|---|---|---|
<Figura> | Imagen con epígrafe y reserva de espacio | Nada |
<Cita> | Frase destacada fuera del cuerpo | Nada |
<Aparte> | Digresión con filete | Nada |
<Video> | Video propio con controles | Nada |
<Nota> | Nota al margen numerada | Sí, es componente cliente |
<YouTube> | Fachada de video | Sí, 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.
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+Fsobre 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.comen 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.
- name: Verificar el frontmatter de las notas
run: npm run verificar-notas
- name: Compilar
run: npm run buildEs 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.