Introduction
Node.js caches modules loaded with require() -- the first call to require('./config') loads and executes the file, but subsequent calls return the cached exports without re-reading the file. This caching is essential for performance but causes issues in development servers, configuration reloaders, and plugin systems where file changes should be reflected without restarting the process. When the require cache is not invalidated, code changes are silently ignored, leading to confusion about why updated code is not taking effect.
Symptoms
File is edited but old code runs:
```javascript // config.js (initially) module.exports = { port: 3000 };
// Server loads config const config = require('./config'); console.log(config.port); // 3000
// Developer edits config.js to port: 4000 // Server reloads (without restart) const config2 = require('./config'); console.log(config2.port); // Still 3000! - cached version ```
Or in a plugin system:
```javascript // Plugin updated on disk fs.writeFileSync('./plugins/auth.js', newCode);
// But the old plugin code still runs const auth = require('./plugins/auth'); auth.verify(token); // Uses old logic ```
Common Causes
- require() returns cached module: Node.js caches modules by resolved path
- File path resolved differently:
./configand/abs/path/configare different cache entries - Native modules cannot be unloaded:
.nodeaddons are never uncachable - Circular dependencies complicate cache deletion: Deleting one module in a circular chain breaks the chain
- ES modules (import) cannot be cleared:
importdoes not have a cache API likerequire - Webpack/Bundler cache: Build tools have their own cache layers
Step-by-Step Fix
Step 1: Delete from require.cache
```javascript function clearRequireCache(modulePath) { const resolvedPath = require.resolve(modulePath); delete require.cache[resolvedPath];
// Also clear any child modules that were loaded by this module Object.keys(require.cache).forEach((key) => { if (key.startsWith(resolvedPath)) { delete require.cache[key]; } }); }
// Usage const config1 = require('./config'); console.log(config1.port); // 3000
clearRequireCache('./config');
const config2 = require('./config'); // Re-reads from disk console.log(config2.port); // 4000 (new value) ```
Step 2: Auto-reload with file watcher
```javascript const fs = require('fs'); const path = require('path');
function createReloader(modulePath) { const fullPath = require.resolve(modulePath);
function load() { clearRequireCache(modulePath); return require(modulePath); }
// Watch for file changes
fs.watchFile(fullPath, { interval: 500 }, () => {
console.log(${path.basename(fullPath)} changed, reloading...);
try {
const freshModule = load();
console.log('Reloaded successfully');
// Notify listeners of the change if (global.reloadListeners) { global.reloadListeners.forEach(fn => fn(freshModule)); } } catch (err) { console.error('Reload failed:', err.message); } });
return load(); }
// Usage const config = createReloader('./config'); ```
Step 3: Use dynamic import for ES modules
javascript
// For ES modules (which cannot use require.cache)
async function loadModule(filePath) {
// Add cache-busting query parameter
const timestamp = Date.now();
const module = await import(${filePath}?t=${timestamp}`);
return module.default;
}
// Usage in an ES module project const freshConfig = await loadModule('./config.js'); ```
Prevention
- Always use
delete require.cache[require.resolve(path)]to invalidate cached modules - Use file watchers in development servers for automatic module reloading
- Consider using
nodemonorts-node-devfor development auto-restart - For production, restart the process instead of hot-reloading modules
- Use dynamic
import()for ES module hot-reloading with cache-busting query parameters - Log cache invalidation events to track which modules are being reloaded
- Add a
/reloadadmin endpoint for configuration hot-reloading in production