Direct Answer
Use <KeepAlive :max="N"> (with a conservative limit like 5–10) to bound the number of cached form instances. To ensure memory safety, perform subscription and timer cleanup in the deactivated hook. For long sessions where memory is strictly limited or forms contain heavy assets (e.g., large file buffers), a forced remount via :key="$route.fullPath" is safer, provided you persist unsaved input to an external store like Pinia or localStorage.
Memory Constraints: Instances vs. Bytes
The max attribute in <KeepAlive> limits the number of VNodes cached, not the total bytes of RAM used. If your forms are lightweight, max is an effective bound. However, if a single form instance consumes significant memory (e.g., via a third-party rich-text editor or large reactive arrays), a small max value may still lead to memory pressure. In such cases, forced remounts are preferable because they trigger the full unmount lifecycle, immediately freeing the instance's memory.
Dividing Cleanup Logic
When a component may be either cached or remounted, cleanup must be handled carefully to avoid memory leaks or double-execution:
- Cached Components:
onBeforeUnmount does not fire when a component is moved to the cache. You must use deactivated to stop timers and unsubscribe from listeners.
- Remounted Components: The standard
onBeforeUnmount or onUnmounted hooks execute normally.
- Hybrid Strategy: For components that behave differently across routes, implement an idempotent cleanup function called by both hooks.
// Idempotent cleanup pattern
let isCleanedUp = false;
const cleanup = () => {
if (isCleanedUp) return;
subscription.unsubscribe();
window.removeEventListener('resize', handleResize);
isCleanedUp = true;
};
onDeactivated(cleanup);
onBeforeUnmount(cleanup);
State Persistence Trade-offs
Unsaved input cannot survive a forced remount using only local component state; the instance is destroyed. To maintain a "draft" experience during forced remounts, you must sync the form state to an external store keyed by the route ID. While <KeepAlive> preserves local state automatically, it is still recommended to flush drafts to a store during deactivated, as the max limit may evict the instance without warning.
Verification and Validation
- Memory Leak Test: Use Chrome DevTools Memory tab. Take a heap snapshot, navigate through more than
max forms, and take a second snapshot. Filter by component name to verify that evicted instances are garbage collected.
- Lifecycle Audit: Add logs to
deactivated and onBeforeUnmount. Confirm that deactivated fires during KeepAlive transitions and onBeforeUnmount fires during keyed remounts.
- State Loss Check: Navigate away from a keyed form with input; return to the route and verify that fields are reset unless a persistence layer is active.
Missing Diagnostic
What is the average retained size of a single form instance in the heap snapshot? This value determines if max is sufficient or if forced remounts are mandatory to prevent browser crashes.