8 diciembre, 2025


Usar ?v con un timestamp que cambia en cada petición impide que el navegador y el CDN cacheen tus CSS/JS. Aprende por qué ocurre y cómo solucionarlo con versionado estable o por mtime.


TL;DR

  • Poner ?v=<?=time();?> en los assets cambia el URL en cada request.
  • Resultado: 0% de reutilización de caché, más descargas, peor LCP/FCP.
  • Soluciones: versión fija por despliegue, filemtime(), hash en nombre de archivo, y cabeceras de caché largas.

¿Qué es el parámetro v y por qué se usa?

Añadir ?v=<?time();?> a un archivo estático (CSS/JS) sirve para forzar la actualización cuando ese archivo cambia. El navegador ve un URL distinto y vuelve a descargarlo.
Bien usado, mejora el control de invalidación. Mal usado, se convierte en un freno de rendimiento.

/res/build/app.css?v=<?time();?>
/res/build/bundle.js?v=<?time();?>

El problema de ?v=<?=time();?>

Cuando el valor de v se genera con el tiempo actual en cada request:

  • El URL de /res/app.css?v=169... nunca se repite.
  • El navegador/CDN no pueden cachearlo de forma efectiva.
  • Cada visita descarga de nuevo todos los CSS/JS: más latencia, más ancho de banda y peor experiencia.

Síntomas que verás en DevTools (Network):

  • Columnas “Size” marcadas como from disk cache / memory cache desaparecen.
  • Cada refresh: 200 OK en vez de 304 Not Modified o caché local.
  • Tiempos de render (FCP/LCP) y TBT peor de lo esperado.

Cómo solucionarlo (sin perder control de invalidación)

1) Versión fija por despliegue

  • Define una versión estática (ej. fecha del release): v=2025-09-26.
  • Solo cambia cuando haces un nuevo despliegue.
  • Ventaja: invalida todos los assets a la vez; simple de mantener.

Ejemplo de URL:

/res/build/app.css?v=2025-09-26
/res/build/bundle.js?v=2025-09-26

2) Versión automática por fecha de modificación (mtime)

  • Usa la marca de tiempo del archivo como versión: si el archivo cambia, cambia el valor.
  • Ventaja: invalida solo el asset que se modificó.

Idea en pseudocódigo:

v = mtime('/ruta/a/app.css')  // p.ej. 1737935400
<link rel="stylesheet" href="/res/build/app.css?v=1737935400">

3) Hash en el nombre del archivo (fingerprinting)

  • Empaquetadores modernos generan nombres como app.33c3168.css.
  • Al cambiar el contenido, cambia el nombre del archivo.
  • Ventaja: URLs inmutables y caché agresiva a 1 año sin riesgos.

Ejemplo:

/res/build/app.33c3168.css
/res/build/vendor.2d03cf9.js

Configura bien las cabeceras de caché

Una vez que tus URLs son estables, pon caché fuerte:

  • Para CSS/JS versionados: Cache-Control: public, max-age=31536000, immutable (1 año; el immutable evita revalidaciones innecesarias)
  • Para HTML (documento principal): Cache-Control: no-cache, must-revalidate (el HTML se revalida, pero sus assets estáticos se sirven desde caché)

Impacto en rendimiento (qué esperar)

  • Menos transferencias: el navegador reutiliza CSS/JS entre páginas y visitas.
  • Menor TTFB aparente de assets y mejor FCP/LCP.
  • Ahorro en CDN y ancho de banda del servidor.

Buenas prácticas y checklist

  • ❌ Evita timestamps que cambien en cada request.
  • ✅ Usa versión fija por release o mtime/hash.
  • ✅ Unifica la estrategia para todos tus assets (CSS, JS, fuentes, imágenes críticas).
  • ✅ Activa Cache-Control largo para archivos versionados.
  • ✅ Verifica en DevTools que la segunda carga muestre cache hits.

¡Va! Aquí tienes ejemplos cortos para cada solución.


1) Versión fija por despliegue

<?php
define('ASSET_VER', '2025-09-26'); // cambia en cada release
?>
<link rel="stylesheet" href="/res/build/app.css?v=<?php echo ASSET_VER; ?>">
<script src="/res/build/app.js?v=<?php echo ASSET_VER; ?>"></script>

2) Versión automática por mtime (fecha de modificación)

<?php
function asset_url(string $rel): string {
    $abs = __DIR__ . '/' . ltrim($rel, '/');
    $ver = is_file($abs) ? filemtime($abs) : time(); // fallback simple
    return '/' . ltrim($rel, '/') . '?v=' . $ver;
}
?>
<link rel="stylesheet" href="<?php echo asset_url('res/build/app.css'); ?>">
<script src="<?php echo asset_url('res/build/app.js'); ?>"></script>

3) Hash en el nombre del archivo (fingerprinting con manifest)

manifest.json (generado por tu build):

{
  "app.css": "app.33c3168.css",
  "app.js": "app.2d03cf9.js"
}

PHP para resolver desde el manifest:

<?php
function asset_from_manifest(string $logical): string {
    static $m = null;
    if ($m === null) {
        $m = json_decode(file_get_contents(__DIR__ . '/res/build/manifest.json'), true) ?? [];
    }
    return '/res/build/' . ($m[$logical] ?? $logical);
}
?>
<link rel="stylesheet" href="<?php echo asset_from_manifest('app.css'); ?>">
<script src="<?php echo asset_from_manifest('app.js'); ?>"></script>

(Recomendado) Cabeceras de caché para assets versionados

Apache (.htaccess)

<FilesMatch "\.(css|js|png|jpg|svg|woff2)$">
  Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>

Nginx

location ~* \.(css|js|png|jpg|svg|woff2)$ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}

Con esto:

  • Fija: invalida todo al cambiar la versión del deploy.
  • mtime: invalida solo el asset que cambió.
  • hash: invalida por contenido y permite caché “fuerte” por 1 año.

Preguntas frecuentes

¿Puedo usar timestamps en desarrollo?
Sí, para forzar recargas durante el desarrollo es cómodo. No lo lleves a producción.

¿Y si un proxy intermedio ignora query strings?
Con hash en el nombre del archivo eliminas esa preocupación y maximizas compatibilidad.

¿Qué pasa si olvido cambiar la versión fija?
El navegador seguirá sirviendo la versión anterior. Por eso, automatiza el cambio o usa mtime/hash.


Conclusión

El parámetro v es útil, pero solo si su valor permanece estable entre peticiones. Cambiarlo cada vez con un timestamp destruye la caché y el rendimiento. Adopta versión por release, mtime o hash en el nombre, y acompáñalo con cabeceras de caché adecuadas. Tu sitio —y tus usuarios— lo notarán.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *