La caché de deps: node_modules/.vite y --force
El pre-bundling se hace una vez y se guarda en node_modules/.vite. Vite decide reusar o rehacer esa caché comparando una huella de entradas: el lockfile, la carpeta de parches y ciertos campos de la config. Cuándo se invalida sola, cuándo forzarla con --force, y por qué a veces hay que borrarla a mano.
Pre-empaquetar cuesta lo suficiente como para que hacerlo en cada arranque fuera un impuesto que nadie quiere pagar. Así que Vite lo hace una vez y lo cachea, y toda la cuestión se reduce entonces a una pregunta afilada: ¿cuándo sigue siendo válida esa caché y cuándo miente? Vite responde con una huella de sus entradas. Entender qué entra en esa huella te dice con exactitud cuándo la caché se refresca sola y cuándo tienes que forzarle la mano.
- Saber dónde vive la caché del pre-bundling y qué guarda exactamente.
- Entender la huella de entradas con la que Vite decide reusar o rehacer.
- Usar
--forcepara reoptimizar sin borrar nada a mano. - Reconocer los casos en que la caché miente y hay que limpiarla.
Dónde vive y qué guarda
La caché del pre-bundling vive en node_modules/.vite. Que esté dentro de node_modules no es casual: la hace desaparecer con un rm -rf node_modules y la mantiene fuera del control de versiones sin esfuerzo, porque node_modules ya está en tu .gitignore. Es un artefacto derivado, y su sitio es junto a lo que deriva.
node_modules/.vite/
├─ deps/ # entorno cliente
│ ├─ _metadata.json # la huella + el mapa de deps optimizadas
│ ├─ react.js
│ └─ lodash-es.js
└─ deps_ssr/ # entorno SSR, optimizado por separado
El archivo clave es _metadata.json. Dentro guarda dos cosas: la huella con la que Vite decidirá si esta caché sigue sirviendo, y el mapa que asocia cada dependencia con su archivo empaquetado y su versión. Cuando arrancas, Vite lee ese archivo antes de tocar cualquier otra cosa, porque es lo que le permite ahorrarse todo el trabajo de los actos de escaneo y bundle si nada ha cambiado.
La escritura de esa caché es cuidadosa: Vite empaqueta en una carpeta temporal y la promueve de golpe al final, para que un arranque interrumpido a mitad no deje una caché medio escrita que luego parezca válida. Es una precaución contra la corrupción, aunque —como veremos— no infalible ante todos los modos de fallo.
El nombre deps corresponde al entorno de cliente; si tu proyecto hace SSR verás también deps_ssr, y con la Environment API puede haber una carpeta por entorno. Cada una tiene su propio _metadata.json y su propia huella, porque una dependencia puede optimizarse distinto según dónde corra. Cuando limpies la caché a mano, recuerda que borrar node_modules/.vite se las lleva todas de golpe, que suele ser justo lo que quieres.
La huella: cuándo se invalida sola
Aquí está el corazón del asunto. En cada arranque, Vite calcula una huella —un hash— de todo aquello que, si cambiara, invalidaría el pre-bundling anterior. Luego la compara con la huella guardada en _metadata.json. Si coinciden, reusa la caché tal cual, en milisegundos. Si difieren, rehace el pre-bundling y reescribe la caché.
¿Qué entra en esa huella? A grandes rasgos, todo lo que puede cambiar qué dependencias tienes o cómo deben empaquetarse:
- El lockfile del gestor de paquetes:
pnpm-lock.yaml,package-lock.jsonoyarn.lock. Es la señal principal; si instalas, actualizas o quitas algo, el lockfile cambia y la huella con él. - La carpeta de parches, si usas
patch-packageo los parches de pnpm: parchear una dependencia cambia su contenido sin tocar su versión, y la huella lo recoge. - Un subconjunto de campos de la config relevantes para la resolución y para el propio
optimizeDeps—tusinclude, tusexclude, las opciones del bundler—, más el modo de ejecución.
flowchart TD A[Arranque de Vite] --> B[Calcula la huella actual] B -->|coincide con la guardada| D[Reusa la cache en milisegundos] B -->|no coincide| E[Rehace el pre-bundling y reescribe] style D fill:#a6e3a1,color:#11111b style E fill:#f9e2af,color:#11111b
Lo que entra y lo que no entra en la huella dibuja la frontera de lo que la caché sabe vigilar, y conviene tenerlo explícito:
| Sí entra en la huella | No entra en la huella |
|---|---|
| lockfile del gestor de paquetes | edición directa de un paquete enlazado |
| carpeta de parches | un paquete instalado con npm link global |
include, exclude, opciones del bundler |
cambios que no tocan lockfile ni parches |
Por eso el disparador más común de una reoptimización es un pnpm install: cambia el lockfile, cambia la huella, y en el siguiente arranque Vite rehace las deps una vez. Ese comportamiento es deseable y silencioso; es el sistema funcionando bien, no un fallo. La primera vez que lo ves puede alarmar, pero es exactamente lo que quieres: la caché se ha dado cuenta sola de que el mundo cambió.
No confundas la caché en disco con la del navegador. En disco está el bundle de cada dependencia. En el navegador está lo que este guarda tras pedir esos archivos, controlado por la versión que Vite cuelga en la URL de cada dependencia. Cuando el pre-bundling se rehace, esa versión cambia, y el cambio de URL invalida la copia del navegador y fuerza una recarga. La huella gobierna la caché de disco; la versión en la URL gobierna la del navegador. Son dos capas encadenadas, y muchos comportamientos confusos se aclaran en cuanto distingues cuál de las dos está actuando.
–force y la limpieza manual
La huella es un modelo de qué podría cambiar el resultado y, como todo modelo, es incompleto. Hay cambios reales que no captura, y ahí es donde la caché miente: pasa el chequeo de huella pero sirve un bundle rancio.
# Reoptimiza incondicionalmente, saltandose el chequeo de huella
vite --force
# El martillo mas grande: borra la cache entera y empieza de cero
rm -rf node_modules/.vite
vite --force ignora la comparación de huellas y rehace el pre-bundling pase lo que pase. Es la herramienta correcta cuando sabes que algo cambió pero la huella no lo vio. Los casos típicos comparten un mismo rasgo: un cambio real que ocurre fuera del alcance de lo que la huella sabe mirar.
- Una dependencia enlazada que editaste en su sitio. Su código cambió, pero ni el lockfile ni los parches lo reflejan, así que la huella no se entera.
- Un paquete enlazado globalmente con
npm linko equivalente, que vive fuera del alcance del lockfile del proyecto. - Una caché corrupta o a medio escribir tras un arranque interrumpido a la fuerza.
- Una actualización de Vite o de un plugin que cambia cómo se genera el bundle sin que el lockfile de tus dependencias de aplicación lo refleje directamente.
En todos ellos, el síntoma es el mismo: sirves algo viejo y no entiendes por qué, porque desde el punto de vista de la huella nada cambió. --force —o, en su defecto, borrar node_modules/.vite— es el reset que reconcilia la caché con la realidad. La diferencia entre ambos es de matiz: --force reoptimiza dentro del arranque normal, mientras que borrar la carpeta es el reinicio total cuando sospechas de corrupción.
Dep enlazada editada
Cambiaste el código de un paquete linkeado en su sitio; ni lockfile ni parches lo reflejan, así que la huella no se entera.
npm link global
Un paquete enlazado fuera del proyecto vive más allá del alcance del lockfile que la huella vigila.
Caché corrupta
Un arranque interrumpido a la fuerza pudo dejar artefactos a medias que aun así pasan el chequeo de huella.
Vite o plugin actualizado
Cambió cómo se genera el bundle sin que el lockfile de tu aplicación lo refleje directamente.
El primer arranque paga; los demás no
Interiorizar la caché reordena tus expectativas de rendimiento. El primer arranque tras instalar dependencias —o tras un --force— hace el trabajo completo de escaneo y bundle, y es notablemente más lento. Todos los arranques siguientes, mientras la huella coincida, reusan la caché y son casi instantáneos. Esa asimetría es la que a veces confunde: el mismo comando tarda segundos una vez y milisegundos la siguiente, y no hay nada roto en ello.
La consecuencia práctica es que no debes alarmarte por un arranque lento aislado justo después de tocar dependencias, ni celebrar demasiado uno rápido: lo que mides en cada caso es un estado distinto de la caché, no la velocidad de fondo de tu proyecto. Para comparar de verdad, iguala el punto de partida borrando la caché antes de cronometrar.
La caché entre máquinas y en CI
Como node_modules/.vite es un artefacto derivado y está en tu .gitignore, no viaja con el repositorio: cada máquina y cada pipeline de CI reconstruye su propia caché en el primer arranque. Esto plantea una tentación y un error a evitar.
La tentación es cachear también node_modules/.vite en CI para saltarse ese primer pre-bundling. A veces compensa, pero con matices: la caché de deps es barata de reconstruir comparada con la instalación de dependencias, así que el ahorro suele ser modesto, y una caché de CI mal invalidada reintroduce justo el problema del bundle rancio, ahora en un entorno donde nadie lo está mirando en vivo.
La práctica robusta es cachear el node_modules completo con una clave derivada del lockfile, y dejar que Vite reconstruya .vite por su cuenta al arrancar. Así la caché de CI se invalida exactamente cuando cambian las dependencias, que es justo el evento que también invalida la huella del pre-bundling:
- uses: actions/cache@v4
with:
path: node_modules
key: deps-${{ hashFiles('pnpm-lock.yaml') }}
Con esa clave, dos ejecuciones con el mismo lockfile comparten node_modules, y el pre-bundling se reconstruye barato encima. Si el lockfile cambia, la clave cambia, la instalación se rehace y la huella del pre-bundling también: las dos capas de caché quedan alineadas por la misma señal —el lockfile— sin que tengas que coordinarlas a mano.
El error a evitar es meter node_modules/.vite en git para “compartir” la caché. Es un artefacto derivado, atado a la máquina, al gestor de paquetes y a la versión de Vite; versionarlo garantiza conflictos y cachés rancias que pasan la huella en la máquina equivocada. Si quieres acelerar CI, cachea el node_modules completo con la clave del lockfile y deja que Vite reconstruya .vite cuando haga falta. La regla es simple: lo derivado se regenera, no se versiona.
Hay una verdad incómoda en el corazón de todo sistema de caché, y el pre-bundling de Vite la ilustra con una claridad de manual: una caché es tan buena como su clave de invalidación, y ni un ápice mejor. Guardar el resultado de un trabajo caro es la parte fácil; la parte difícil, la que separa una caché que ayuda de una que te traiciona una tarde entera, es saber con precisión cuándo ese resultado ha dejado de ser válido. La huella que Vite calcula —lockfile, parches, campos de config— no es más que su respuesta explícita a esa pregunta: su modelo de todo lo que, si cambia, obliga a rehacer. Y como todo modelo, traza una frontera. Del lado de dentro, los cambios que la huella captura, y ahí la caché se refresca sola con una fiabilidad total. Del lado de fuera, los cambios que la huella no puede ver —una dependencia enlazada editada en el sitio, un paquete linkeado globalmente, un artefacto tocado a mano— y ahí la caché falla en silencio, sirviendo lo viejo con total convicción. Toda la fenomenología de borra node_modules/.vite y vuelve a probar, ese consejo que circula por los issues como un conjuro, es en realidad gente tropezando con el límite de ese modelo sin llegar a nombrarlo. --force no es magia ni un martillo bruto: es la admisión honesta de que la huella es una aproximación, y la puerta de emergencia para cuando tu cambio cae fuera de lo que ella sabe mirar. La lección trasciende con mucho a Vite. Cada vez que construyas o dependas de una caché —de build, de HTTP, de datos, de lo que sea— la pregunta que decide si te va a ayudar o a arruinar la jornada no es qué guarda, sino qué la invalida y qué cambios reales quedan fuera de esa cuenta. Quien interioriza que el valor de una caché vive enteramente en su clave de invalidación deja de sorprenderse con las cachés rancias y empieza a predecir, casi con puntería, exactamente cuándo aparecerán y por qué. Esa es la diferencia entre padecer las cachés y diseñarlas.
- Arranca Vite, páralo y vuelve a arrancar: confirma en consola que el segundo arranque reusa la caché y es mucho más rápido.
- Instala una dependencia nueva y observa cómo el cambio de lockfile dispara una reoptimización sola en el siguiente arranque.
- Abre
_metadata.jsonantes y después de esa instalación y compara la huella para ver qué cambió. - Enlaza un paquete, edítalo en su sitio y comprueba que Vite sigue sirviendo lo viejo; arréglalo con
vite --force. - Revisa tu configuración de CI y decide con criterio si cacheas
node_modulespor la clave del lockfile, y por qué no versionasnode_modules/.vite.