Introduction

When using code splitting with dynamic imports (import()), the browser downloads JavaScript chunks on demand. After a new deployment, old chunk files are deleted from the server, causing 404 errors for users who loaded the previous version and later navigate to a route that triggers a lazy load:

bash
GET https://example.com/static/js/123.a1b2c3.chunk.js 404 (Not Found)
ChunkLoadError: Loading chunk 123 failed.
(error: https://example.com/static/js/123.a1b2c3.chunk.js)

This breaks navigation and leaves users with a partially functional application.

Symptoms

  • "ChunkLoadError" or "Loading chunk failed" error in the browser console
  • Navigation to certain routes fails after a deployment
  • 404 errors for JavaScript chunk files with old content hashes
  • Application works after a full page refresh
  • Error only affects users who had the app open during the deployment

Common Causes

  • Deployment deletes old chunk files from the server/CDN
  • User's SPA loaded the old version's HTML (which references old chunk hashes)
  • Dynamic import() resolves to a chunk that no longer exists on the server
  • Service Worker caches old chunk references
  • Build tool generates new chunk hashes with every build, even without code changes

Step-by-Step Fix

  1. 1.Handle chunk load errors globally with an error boundary and retry:
  2. 2.```javascript
  3. 3.// React example with error boundary
  4. 4.class ChunkReloadErrorBoundary extends React.Component {
  5. 5.state = { hasError: false };

static getDerivedStateFromError(error) { if (error.message && error.message.includes('Loading chunk')) { return { hasError: true }; } return { hasError: false }; }

render() { if (this.state.hasError) { window.location.reload(); return null; } return this.props.children; } } ```

  1. 1.Catch chunk load errors in dynamic imports:
  2. 2.```javascript
  3. 3.const LazyComponent = React.lazy(() =>
  4. 4.import('./HeavyComponent').catch((error) => {
  5. 5.if (error.message.includes('Loading chunk')) {
  6. 6.// New version deployed, reload the page
  7. 7.window.location.reload();
  8. 8.return new Promise(() => {}); // Return a never-resolving promise
  9. 9.}
  10. 10.throw error;
  11. 11.})
  12. 12.);
  13. 13.`
  14. 14.Implement version detection to check for new deployments periodically:
  15. 15.```javascript
  16. 16.async function checkForNewVersion() {
  17. 17.try {
  18. 18.const response = await fetch('/version.json', { cache: 'no-store' });
  19. 19.const { hash } = await response.json();
  20. 20.const currentHash = window.__BUILD_HASH__;

if (hash && hash !== currentHash) { showUpdateNotification('A new version is available. Reload to update.'); } } catch (e) { // Silently fail version check } }

// Check every 5 minutes setInterval(checkForNewVersion, 5 * 60 * 1000); ```

  1. 1.Configure webpack chunk filename for debugging:
  2. 2.```javascript
  3. 3.module.exports = {
  4. 4.output: {
  5. 5.chunkFilename: '[name].[contenthash:8].chunk.js',
  6. 6.},
  7. 7.};
  8. 8.`
  9. 9.Set up a global webpack chunk load error handler:
  10. 10.```javascript
  11. 11.// In your entry point file
  12. 12.window.addEventListener('unhandledrejection', (event) => {
  13. 13.if (
  14. 14.event.reason &&
  15. 15.event.reason.message &&
  16. 16.event.reason.message.includes('Loading chunk')
  17. 17.) {
  18. 18.event.preventDefault();
  19. 19.window.location.reload();
  20. 20.}
  21. 21.});
  22. 22.`

Prevention

  • Always implement chunk load error handling in applications that use code splitting
  • Generate a version.json file during build containing the git commit hash for deployment tracking
  • Keep old chunk files on the CDN for at least 24 hours after deployment to cover users with open tabs
  • Use Service Worker strategies that cache the shell but use network-first for chunks
  • Test deployment scenarios by loading the app, deploying a new version, then navigating to lazy-loaded routes
  • Add chunk load error monitoring to your error tracking (Sentry, LogRocket) to detect deployment-related issues