Metal Archive - Catalogo de Metal Underground

Catalogo web de 1300+ albums de metal underground vinculado a un canal de YouTube. Sincroniza automaticamente los videos del canal, enriquece los datos con artwork de alta calidad, normaliza generos y paises, y ofrece busqueda, descubrimiento y un reproductor integrado.

Originalmente construido con Reflex (Python full-stack). Migrado a Astro SSG tras un problema de rendimiento de fondo: Reflex renderiza todo via WebSocket, asi que cada pagina bajaba ~1MB de JS + 687KB de CSS y abria un WebSocket para hidratar el estado antes de pintar un solo album. Como el catalogo es contenido estatico (igual para todos, cambia cada 12h), la herramienta correcta era un sitio estatico, no una app con estado en tiempo real.

Resultado de la migracion

MetricaReflex (antes)Astro (ahora)
Carga de pagina1MB JS + 687KB CSS + hydration ~967msHTML completo en ~0.2s
JS en paginas de lectura~1MB (socket.io + state engine)0 bytes
Contenidoshell vacio que hidrataba por WebSocketrenderizado en el HTML
Build catalogo completon/a (todo en runtime)1679 paginas en ~3.5s

Arquitectura (Astro hibrido)

Diagrama de arquitectura hibrida


Decisiones tecnicas clave

SSG puro, no ISR. El catalogo son ~1500 paginas. Un spike midio que generarlas todas tarda ~2.5s, asi que ISR/on-demand solo agregaria complejidad (cache, cold starts) sin beneficio a esta escala. Build completo bajo 5s.

Build local + vercel deploy --prebuilt. El build necesita reflex.db, que vive solo en el host (gitignored). En vez de subir la DB a la nube, el build corre localmente (donde estan los datos) y se publica el output pre-construido a Vercel. Cero infra nueva.

Theming dinamico en build time. Cada pagina de album extrae el color dominante de su portada con node-vibrant y lo hornea como variable CSS (--album-color) — el fondo “se tine” del color del album sin JS en runtime. Las portadas .webp (que node-vibrant/Jimp no decodifica) se procesan con sharp; la extraccion respeta rate-limits del CDN con backoff. Resultado: 1298/1298 portadas con color real, cacheadas para builds incrementales.

Autoplay sin perder el user-gesture. El reproductor es un island de Preact que controla el IFrame Player de YouTube. El click en una cancion llama seekTo + playVideo de forma sincronica en el handler (sin round-trip a un backend), preservando el gesto del usuario que exigen las politicas de autoplay del navegador. Auto-avance por timestamp: como cada album es un solo video, el progreso compara contra el inicio de la siguiente pista y avanza el nombre/resaltado al cruzar cada limite.

Solo lo interactivo lleva JS. Astro envia 0 JS por defecto. Los unicos islands son el reproductor (en paginas de album) y el buscador (en browse). Todo lo demas — home, facetas, paginas de banda — es HTML estatico puro.


Pipeline de Sincronizacion (sin cambios desde Reflex)

Un daemon thread sincroniza el canal de YouTube con la base de datos cada 12 horas:

Pipeline de sincronización

La autenticacion paso de OAuth (refresh tokens que expiraban cada pocos meses) a una API Key que no expira — solo lee datos publicos del canal, asi que OAuth era innecesario.


Funcionalidades

  • Reproductor integrado con tracklist navegable, now-playing bar, mini-player flotante y estados de carga/error.
  • Theming por album — el color de cada portada tine la pagina.
  • Paginas de banda (estilo Navidrome) — el nombre de banda es clickeable cuando tiene 2+ trabajos y lleva a su discografia.
  • Live Recordings separadas del catalogo oficial (grabaciones personales en vivo), con portada propia.
  • Busqueda client-side sobre un indice JSON ligero (sin llamadas al servidor).
  • Descubrimiento: Editor’s Picks, Hidden Gems, showcases por genero, rotacion por pais, boton “Surprise Me”.
  • Formularios (submit/promo/newsletter/contact) via FastAPI con notificacion por Gmail SMTP.

Stack Tecnologico

CategoriaTecnologias
FrontendAstro 6 (SSG) + islands de Preact + TypeScript
EstilosCSS propio (design system “Xerox Underground”)
Datos (build)better-sqlite3 sobre SQLite (read-only)
Themingnode-vibrant + sharp (extraccion de color en build)
FormulariosFastAPI (host) + Gmail SMTP
SyncPython daemon (12h) — YouTube Data API v3 (API Key)
ArtworkDeathGrind.club API, Metal Archives API, YouTube HD
DeployBuild local + Vercel (--prebuilt) + Cloudflare Tunnel