2 enero, 2026


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.

author avatar
blogdecomputo.com

Deja un comentario

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