Caching the installed directories means a stale cache silently ships the wrong dependency versions; caching the package manager’s download cache does not.
cache:
key:
files: [composer.lock, package-lock.json]
paths:
- .composer-cache/
- .npm/
before_script:
- composer config cache-dir .composer-cache
- composer install --no-interaction --prefer-dist
- npm ci --cache .npm --prefer-offline
Keying on the lock files means a dependency change gets a fresh cache automatically rather than needing a manual clear. npm ci deletes node_modules and installs from the lock file exactly, so caching the download directory speeds it up without any risk of a stale tree — which is the distinction that makes caching safe. Caching node_modules itself is faster and is how a CI run ends up with a package nobody declared.