22

Module 22: Debugging Real Production Issues (Senior/Enterprise)

Chapter 22 • Advanced

45 min

Debugging Real Production Issues (Senior/Enterprise)

The Real Problem

Scenario: Production is broken. Users see blank screens. Console shows "Failed to load remoteEntry.js". You have 2 hours to fix it.

The Question: How do I debug Module Federation issues in production when remotes fail to load, versions conflict, or shared dependencies break?

This is where experience matters.


The Production Reality

Common Production Issues

  1. Remote Loading Failures - remoteEntry.js fails to load
  2. Version Conflicts - Shared dependencies conflict
  3. CORS Errors - Cross-origin issues
  4. Source Map Issues - Can't debug in production
  5. Network Timeouts - Slow or failed network requests
  6. Memory Leaks - Apps not unloading properly

The Question: How do we debug each issue?


Issue 1: Remote Loading Failures

Symptoms

  • Blank screen
  • Console error: "Failed to load remoteEntry.js"
  • Network tab shows 404 or timeout

Debugging Steps

Step 1: Check Network Tab

typescript.js
// Open Chrome DevTools → Network tab
// Look for remoteEntry.js request
// Check:
// - Status code (404, 500, timeout?)
// - Response headers
// - Request URL (correct?)

Step 2: Verify Remote URL

typescript.js
// Host App - Log remote URLs
console.log('Remote URLs:', {
  products: 'products@https://products.example.com/remoteEntry.js',
  checkout: 'checkout@https://checkout.example.com/remoteEntry.js'
})

// Manually test URL in browser
// https://products.example.com/remoteEntry.js
// Should return JavaScript file

Step 3: Check Remote Health

typescript.js
// Test remote endpoint
async function testRemote(url: string) {
  try {
    const response = await fetch(url, { method: 'HEAD' })
    console.log('Remote status:', response.status)
    console.log('Remote headers:', Object.fromEntries(response.headers))
    return response.ok
  } catch (error) {
    console.error('Remote test failed:', error)
    return false
  }
}

// Test all remotes
await testRemote('https://products.example.com/remoteEntry.js')
await testRemote('https://checkout.example.com/remoteEntry.js')

Step 4: Check CORS

typescript.js
// CORS error in console?
// Check remote server CORS headers:
// Access-Control-Allow-Origin: *
// Access-Control-Allow-Methods: GET, HEAD

Solution: Add Error Handling

typescript.js
// Host App - Better error handling
loadChildren: () => import('products/ProductsModule')
  .then(m => m.ProductsModule)
  .catch(err => {
    // Log detailed error
    console.error('Failed to load Products remote:', {
      error: err,
      url: 'products@https://products.example.com/remoteEntry.js',
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent
    })
    
    // Send to error tracking
    errorTracking.trackError(err, {
      source: 'remote-loading',
      remote: 'products'
    })
    
    // Return fallback
    return import('./fallback/products-fallback.module')
      .then(m => m.ProductsFallbackModule)
  })

Issue 2: Version Conflicts

Symptoms

  • Runtime errors: "Cannot read property of undefined"
  • Type errors in console
  • App works in dev, breaks in production

Debugging Steps

Step 1: Check Shared Dependencies

typescript.js
// Check what versions are loaded
console.log('Angular versions:', {
  '@angular/core': require('@angular/core/package.json').version,
  '@angular/common': require('@angular/common/package.json').version
})

// Check webpack shared scope
console.log('Shared scope:', __webpack_share_scopes__)

Step 2: Verify Webpack Config

typescript.js
// Host App - webpack.config.js
shared: {
  '@angular/core': {
    singleton: true,
    strictVersion: true, // Should be true for Angular
    requiredVersion: '^15.0.0' // Check this matches
  }
}

Step 3: Check for Multiple Instances

typescript.js
// Debug: Check if multiple instances loaded
if (window.__ANGULAR_CORE__) {
  console.warn('Multiple Angular core instances detected!')
  console.log('Instances:', window.__ANGULAR_CORE__)
}

Solution: Fix Version Conflicts

typescript.js
// Option 1: Enforce strict versioning
shared: {
  '@angular/core': {
    singleton: true,
    strictVersion: true, // Enforce matching versions
    requiredVersion: '^15.0.0'
  }
}

// Option 2: Allow version tolerance (risky)
shared: {
  '@angular/core': {
    singleton: true,
    strictVersion: false, // Allow different versions
    requiredVersion: '^15.0.0 || ^16.0.0'
  }
}

Issue 3: Source Map Issues

Symptoms

  • Can't debug in production
  • Stack traces show minified code
  • Breakpoints don't work

Solution: Enable Source Maps

typescript.js
// webpack.config.js
module.exports = {
  devtool: 'source-map', // Or 'hidden-source-map' for production
  
  // Or for production:
  devtool: process.env.NODE_ENV === 'production' 
    ? 'hidden-source-map' // Source maps, but not exposed
    : 'eval-source-map'   // Fast for development
}

Debug Production with Source Maps

typescript.js
// 1. Build with source maps
ng build --source-map

// 2. Upload source maps to error tracking service
// (Sentry, LogRocket, etc.)

// 3. Don't expose source maps publicly
// Use hidden-source-map or upload to private service

Issue 4: Network Timeouts

Symptoms

  • Slow loading
  • Timeouts in console
  • Intermittent failures

Debugging Steps

Step 1: Measure Load Times

typescript.js
// Measure remote load time
async function measureRemoteLoad(name: string, url: string) {
  const start = performance.now()
  
  try {
    const response = await fetch(url, {
      method: 'HEAD',
      signal: AbortSignal.timeout(10000) // 10 second timeout
    })
    
    const duration = performance.now() - start
    console.log(`${name} load time: ${duration}ms`)
    
    return { success: true, duration }
  } catch (error) {
    const duration = performance.now() - start
    console.error(`${name} failed after ${duration}ms:`, error)
    return { success: false, duration, error }
  }
}

// Measure all remotes
await measureRemoteLoad('products', 'https://products.example.com/remoteEntry.js')
await measureRemoteLoad('checkout', 'https://checkout.example.com/remoteEntry.js')

Step 2: Check CDN Performance

typescript.js
// Test CDN performance
const locations = [
  'us-east-1',
  'eu-west-1',
  'ap-southeast-1'
]

for (const location of locations) {
  const url = `https://cdn-${location}.example.com/remoteEntry.js`
  await measureRemoteLoad(`CDN ${location}`, url)
}

Solution: Add Timeouts and Retries

typescript.js
// Host App - Timeout with retry
async function loadRemoteWithRetry(
  name: string,
  url: string,
  maxRetries: number = 3
): Promise<any> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const controller = new AbortController()
      const timeoutId = setTimeout(() => controller.abort(), 10000)
      
      const response = await fetch(url, {
        signal: controller.signal
      })
      
      clearTimeout(timeoutId)
      
      if (response.ok) {
        return await response.text()
      }
    } catch (error) {
      console.warn(`${name} attempt ${attempt} failed:`, error)
      
      if (attempt < maxRetries) {
        // Exponential backoff
        await new Promise(resolve => 
          setTimeout(resolve, 1000 * Math.pow(2, attempt - 1))
        )
      } else {
        throw error
      }
    }
  }
}

Issue 5: Memory Leaks

Symptoms

  • Browser slows down over time
  • Memory usage increases
  • Apps not unloading

Debugging Steps

Step 1: Check Memory Usage

typescript.js
// Chrome DevTools → Memory tab
// Take heap snapshot before navigation
// Navigate to remote
// Take heap snapshot after
// Compare: Look for retained objects

Step 2: Check Event Listeners

typescript.js
// Check for leaked event listeners
// Chrome DevTools → Performance tab
// Record → Navigate → Stop
// Look for event listeners not cleaned up

Solution: Proper Cleanup

typescript.js
// Remote App - Cleanup on destroy
export class ProductsComponent implements OnDestroy {
  private subscriptions = new Subscription()
  
  constructor(private eventBus: EventBus) {
    // Subscribe
    const sub = this.eventBus.on('cart:updated').subscribe(...)
    this.subscriptions.add(sub)
  }
  
  ngOnDestroy() {
    // Cleanup subscriptions
    this.subscriptions.unsubscribe()
    
    // Cleanup event listeners
    // Cleanup timers
    // Cleanup DOM references
  }
}

Debugging Tools

Chrome DevTools

  1. Network Tab - Check remote loading
  2. Console Tab - Check errors
  3. Sources Tab - Debug with source maps
  4. Application Tab - Check localStorage, cookies
  5. Memory Tab - Check memory leaks

Webpack Bundle Analyzer

bash.js
# Install
npm install --save-dev webpack-bundle-analyzer

# Analyze
npx webpack-bundle-analyzer dist/host-app/stats.json

Module Federation DevTools

typescript.js
// Add to Host App
if (!environment.production) {
  // Log Module Federation info
  console.log('Module Federation Info:', {
    remotes: __webpack_require__.cache,
    shared: __webpack_share_scopes__
  })
}

Production Debugging Checklist

  • [ ] Network tab checked for remote loading
  • [ ] Console errors reviewed
  • [ ] Source maps enabled (hidden)
  • [ ] Error tracking configured
  • [ ] Performance monitoring active
  • [ ] Memory leaks checked
  • [ ] CORS issues resolved
  • [ ] Version conflicts identified

Key Takeaways

  1. Network tab first - Check remote loading
  2. Console errors - Read error messages carefully
  3. Source maps - Enable for production debugging
  4. Error tracking - Log errors automatically
  5. Performance monitoring - Measure load times

Remember: Debugging production issues requires systematic investigation. Start with network, then console, then code.

The next module: Migration from monolith to micro frontends.