CreaRack-SL

Fix de flash de tipografía con caché inmutable (v1.45.3)

Resumen

Resolución del problema reportado por usuario (Edu) en Edge: al recargar la página, las tipografías de marca (Oswald, Plus Jakarta Sans) desaparecían brevemente y se mostraba la fuente de respaldo del sistema, generando un “baile” visual disruptivo. Root cause: los estáticos se servían con Cache-Control: max-age=60 (default de WhiteNoise sin hash), combinado con font-display: optional (ventana de ~100 ms de espera), causaba que cada recarga “en frío” mostrara la fuente de respaldo.

Fix: en producción, STORAGES["staticfiles"] usa ahora ForgivingManifestStaticFilesStorage (subclase custom que hashea nombres → WhiteNoise sirve con max-age=31536000, immutable). Tras la 1ª visita, la fuente de marca queda cacheada de forma estable e inmutable.

Beneficio colateral: cache-busting automático por hash en cada deploy para todos los estáticos.


Cambios de infraestructura

Storage de estáticos (prod/stage)

  • Archivo: config/settings/production.py — nueva sección “Estáticos con nombres hasheados” que sobreescribe STORAGES["staticfiles"] con backend custom.

  • Backend: core.storage.ForgivingManifestStaticFilesStorage — subclase de CompressedManifestStaticFilesStorage con:

    • manifest_strict = False → tolera lookups sin entrada en el manifest (devuelve el nombre original sin error).
    • post_process() override → degrada MissingFileError de archivos de terceros (p.ej. FontAwesome all.min.css con referencias no resolubles) a “no reescribir ese CSS” en vez de abortar el build.
  • Alcance: solo prod/stage (requieren collectstatic para generar el manifest).

  • Dev/test: heredan CompressedStaticFilesStorage de base.py (sin manifest, sin hash).

Dockerfile.prod

  • Cambio: se quitó || true del comando collectstatic (línea 91).
  • Motivo: con manifest, un collectstatic incompleto deja el staticfiles.json sin generar → cada {% static %} fallaría en runtime con 500. Fail-fast permite detectar el problema en build time (Docker).
  • Verificado en Docker: collectstatic RC=0, staticfiles.json generado, fuentes con hash.

Impacto en caché y recarga

AspectoAntesDespués
Cache Control (estáticos)max-age=60max-age=31536000, immutable
Nombre de archivo (fuentes)plus-jakarta-sans-latin.woff2plus-jakarta-sans-latin.a1b2c3d4e5f6.woff2
FOUT en recarga (100 ms)✗ Font de respaldo✓ Font de marca (caché)
CLS después de 1ª visitaN/A0 (font-display: optional intacto)

Contexto: el problema de FOUT

FOUT = “Flash of Unstyled Text”. En navegadores con font-display: optional:

  • Si la web font llega en < 100 ms, se usa.
  • Si no llega en < 100 ms, se usa la fuente de respaldo (fallback del sistema) sin esperar más.

Antes del fix:

  1. Visita 1: White Noise sirve con max-age=60 → 1ª vez la fuente está en la red, caché local vacío → FOUT → fallback del sistema.
  2. Visita 2 (en < 60 s): fuente caché está fresca → llega en < 100 ms → se ve la fuente de marca.
  3. Visita 3 (> 60 s después de visita 2): caché expirada (60 s) → fuente baja de la red → FOUT nuevamente.

La caché de 60 s es arbitraria — cada usuario veía el “baile” según cuándo recargara.

Después del fix:

  1. Visita 1: nombre hasheado + max-age=31536000, immutable → fuente se descarga y se cachea por 1 año.
  2. Visitassubsecuentes: fuente en caché local = < 100 ms → siempre se ve la fuente de marca. Sin FOUT.
  3. Deploy nuevo (nuevo hash): navegador trata el nombre como nuevo archivo → descarga la nueva fuente sin contaminar la caché vieja.

Entidades relacionadas

  • Storage backend: [[entity—core—service—forgiving-manifest-storage]]
  • Configuración de producción: config/settings/production.py (STORAGES override).
  • Dockerfile: Dockerfile.prod (collectstatic fail-fast).
  • Font loading: CSS con font-display: optional en base de maquetación (sin cambios).

Véase también

  • [[entity—core—service—forgiving-manifest-storage]]
  • [[concept—infra—cache-busting]]
  • [[concept—ux—font-display]]
  • [[decision—20260704—manifest-storage-prod]]