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()
ormounted()
are removed inonBeforeUnmount()
orbeforeUnmount()
- 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)
})
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()
})
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)
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)
}
}
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)
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
}
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)
}
}
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)
}
}
})
Review and Optimize Pinia Store Usage
If you're using Pinia with persistence, review your implementation:
- Check for deep nested objects that might be causing issues
- Consider using more granular stores
- Implement proper reset methods
Check for Redis Connection Management
If you're using Redis (ioredis):
- Ensure connections are properly closed
- Use a connection pool
- Monitor active connections
Remember that fixing memory leaks is often an iterative process. Monitor, fix, and repeat until the memory usage stabilizes.