Nuxt 3: Memory Leak Analysis and Fixes
Pradeep Kumar

Pradeep Kumar @themodernpk

About: 𝗧𝗲𝗰𝗵𝗶𝗲 + 𝗚𝗲𝗲𝗸 + 𝗘𝗻𝘁𝗿𝗲𝗽𝗿𝗲𝗻𝗲𝘂𝗿 | Your perception of life is your life | Dreams don't work unless you do | Don't freak out, figure out!

Location:
Delhi, India
Joined:
Aug 31, 2019

Nuxt 3: Memory Leak Analysis and Fixes

Publish Date: May 14
3 0

Common Memory Leak Sources in Nuxt Applications

1. Event Listeners Not Being Removed

Problem: Event listeners that aren't properly cleaned up when components are unmounted.

Fix:

  • Ensure all event listeners added in onMounted() or mounted() are removed in onBeforeUnmount() or beforeUnmount()
  • Check for global event listeners in particular

Example:

// Bad
onMounted(() => {
  window.addEventListener('resize', handleResize)
})

// Good
onMounted(() => {
  window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
  window.removeEventListener('resize', handleResize)
})
Enter fullscreen mode Exit fullscreen mode

2. Watchers Not Being Stopped

Problem: Watchers created outside of component options continue running even after component unmount.

Fix:

  • Store watcher stop functions and call them in onBeforeUnmount()

Example:

// Bad
const stopWatcher = watch(someValue, () => {
  // ...
})

// Good
const stopWatcher = watch(someValue, () => {
  // ...
})
onBeforeUnmount(() => {
  stopWatcher()
})
Enter fullscreen mode Exit fullscreen mode

3. Pinia Store Issues

Problem: Pinia stores with persistence can accumulate state over time.

Fix:

  • Reset store state when appropriate
  • Be careful with deep nested objects in store state
  • Consider using shallow refs for large objects

Example:

// Add a reset method to your store
const useMyStore = defineStore('myStore', {
  state: () => ({
    items: [],
    // ...
  }),
  actions: {
    reset() {
      this.$reset() // Reset to initial state
    }
  }
})

// Call reset when appropriate (e.g., user logout)
Enter fullscreen mode Exit fullscreen mode

4. Redis Connection Leaks

Problem: Redis connections not being properly closed.

Fix:

  • Ensure Redis connections are properly managed
  • Use connection pooling
  • Close connections when done

Example:

// Create a connection manager
const redisConnections = new Map()

// Get a connection
function getRedisConnection(key) {
  if (!redisConnections.has(key)) {
    redisConnections.set(key, new Redis(/* config */))
  }
  return redisConnections.get(key)
}

// Close all connections
function closeAllRedisConnections() {
  for (const [key, connection] of redisConnections.entries()) {
    connection.quit()
    redisConnections.delete(key)
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Circular References

Problem: Objects referencing each other creating circular references that GC can't clean up.

Fix:

  • Avoid circular references
  • Use WeakMap/WeakSet for parent-child relationships
  • Manually break references when no longer needed

Example:

// Bad
const parent = {}
const child = { parent }
parent.child = child

// Good
const parent = {}
const child = {}
const parentChildMap = new WeakMap()
parentChildMap.set(child, parent)
Enter fullscreen mode Exit fullscreen mode

6. Closures Capturing Large Objects

Problem: Function closures capturing references to large objects.

Fix:

  • Be mindful of what variables your closures capture
  • Nullify references when done

Example:

// Bad
function processData(largeData) {
  const callback = () => {
    // This closure captures largeData
    console.log(largeData.length)
  }
  return callback
}

// Good
function processData(largeData) {
  const size = largeData.length // Only capture what you need
  const callback = () => {
    console.log(size)
  }
  return callback
}
Enter fullscreen mode Exit fullscreen mode

Specific Fixes for Your Application

Implement Garbage Collection Trigger

Add a button to manually trigger garbage collection during development:

async function forceGarbageCollection() {
  try {
    await fetch('/api/debug/gc', { method: 'POST' })
    console.log('Garbage collection triggered')
    // Refresh memory data after GC
    setTimeout(fetchMemoryData, 500)
  } catch (error) {
    console.error('Failed to trigger garbage collection:', error)
  }
}
Enter fullscreen mode Exit fullscreen mode

Implement Memory Leak Detection in Development

Add a development plugin that detects potential memory leaks:

// plugins/memory-leak-detector.js
export default defineNuxtPlugin({
  name: 'memory-leak-detector',
  enforce: 'pre',
  setup() {
    if (process.dev) {
      let componentCount = new Map()

      // Track component creation and destruction
      vueApp.mixin({
        created() {
          const name = this.$options.name || 'Anonymous'
          componentCount.set(name, (componentCount.get(name) || 0) + 1)
        },
        beforeUnmount() {
          const name = this.$options.name || 'Anonymous'
          componentCount.set(name, (componentCount.get(name) || 0) - 1)
        }
      })

      // Periodically check for leaks
      setInterval(() => {
        for (const [name, count] of componentCount.entries()) {
          if (count > 0) {
            console.warn(`Potential memory leak: ${count} instances of ${name} not destroyed`)
          }
        }
      }, 10000)
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

Review and Optimize Pinia Store Usage

If you're using Pinia with persistence, review your implementation:

  1. Check for deep nested objects that might be causing issues
  2. Consider using more granular stores
  3. Implement proper reset methods

Check for Redis Connection Management

If you're using Redis (ioredis):

  1. Ensure connections are properly closed
  2. Use a connection pool
  3. Monitor active connections

Remember that fixing memory leaks is often an iterative process. Monitor, fix, and repeat until the memory usage stabilizes.

Comments 0 total

    Add comment