State Persistence and Rehydration
State persistence stores selected application state beyond the current session, while rehydration restores that state when the application starts. Robust implementations validate persisted data and handle stale, missing, corrupted, or incompatible values safely.
Persistence separates long-lived state from the in-memory state of a running application. Common browser mechanisms include `localStorage`, `sessionStorage`, IndexedDB, and application-specific storage layers. The choice depends on data size, lifetime, synchronization needs, and whether structured or asynchronous storage is required.
Rehydration is the startup process that reads persisted data and incorporates it into the application's initial state. A store may begin with defaults, asynchronously load persisted values, validate them, and then transition to a hydrated state. Components should account for this lifecycle when rendering or making requests.
Persisting every piece of state is usually undesirable. Transient UI state such as an open menu, temporary form state, or loading indicator often should remain in memory, while durable preferences, selected filters, or carefully chosen cached data may be persisted. Persist only the minimum data needed to provide continuity.
Persisted data should be treated as untrusted input even when it was written by the application itself. Validate its shape, apply defaults, handle parsing failures, and consider a schema or version field so old representations can be migrated or discarded. Sensitive information also requires special care because browser storage is accessible to JavaScript running in the page.
Persistence can introduce consistency and concurrency concerns. Multiple tabs may modify the same storage, writes can race with newer state, and schema changes can make old records incompatible. A production design therefore defines ownership, serialization, versioning, expiration or invalidation rules, and behavior when hydration fails.
Key points
- Persist only state that provides meaningful continuity across sessions.
- Rehydration should validate and safely merge persisted values with defaults.
- Schema versions enable migration or deliberate invalidation of old data.
- Storage failures, malformed data, and unavailable storage must not prevent the application from starting.
- Avoid putting secrets or highly sensitive data into browser-accessible storage without a deliberate security model.
Interview questions
What is rehydration in frontend state management?
Rehydration is the process of taking state saved outside the current runtime and restoring it into the application's active state. For example, an application can read a saved theme preference from browser storage during startup. The restored value should be checked before being used. If it is missing or invalid, the application should use a safe default.
What state would you persist in a frontend application, and what would you avoid persisting?
I would persist state that provides useful continuity, such as user preferences or carefully selected filters. I would generally avoid transient state such as loading flags, temporary UI visibility, and data that can be cheaply recomputed. I would also avoid persisting secrets or sensitive information in browser-accessible storage unless the security model explicitly permits it. The persisted subset should have a defined schema and lifecycle.
How do you handle a persisted state schema after the application is updated?
I would include a schema version with the persisted representation. On startup, the application can detect older versions and migrate them when a safe migration exists. If migration is impossible or the data is no longer meaningful, it can be discarded and replaced with defaults. Migrations should be tested because they become part of the application's compatibility surface.
What problems can multiple browser tabs create for persisted state?
Multiple tabs can independently read and write the same persisted data, so an older tab may overwrite a newer value. Cross-tab changes can also leave in-memory state inconsistent with storage. I would define an ownership and synchronization strategy, potentially using browser storage events or another coordination mechanism, and ensure writes do not blindly overwrite newer state. The design should also account for initialization races and conflict semantics.
How would you design robust persisted state for a large production frontend?
I would first define which state is durable, its schema, ownership, lifetime, and security requirements. I would isolate persistence behind a storage layer so serialization, validation, migration, expiration, and failure handling are centralized rather than scattered across components. Hydration would be observable and resilient, with the application able to start using defaults when persistence is unavailable. I would also test corrupted data, schema migrations, quota or storage failures, reloads, and multi-context synchronization where relevant.