State Management in Modern Web Apps
Web applications have two main types of state: client-side (what the browser knows) and server-side (what the server knows). Managing both correctly is one of the biggest challenges in web development.
Client-Side State
Client-side state lives in the browser. It can be:
Local Component State
const [count, setCount] = useState(0);Used for UI-only state like form inputs, toggles, modals. Resets on page refresh.
Global State
// Redux, Zustand, Context API
const [user, setUser] = useAuth();Shared across components. Persists across navigations. May or may not persist across refreshes.
URL State
// /products?category=books&sort=price
const searchParams = new URLSearchParams(window.location.search);State encoded in the URL — always serializable and bookmarkable.
Browser Storage
localStorage.setItem("theme", "dark");
sessionStorage.setItem("formDraft", text);| Storage | Persists on restart? | Sent to server? | Size limit |
|---|---|---|---|
| localStorage | ✅ Yes | ❌ No | ~5-10 MB |
| sessionStorage | ❌ No (tab closes) | ❌ No | ~5-10 MB |
| Cookies | ✅ Configurable | ✅ Yes (every request) | ~4 KB |
Server-Side State
Server-side state lives on the server — typically in a session store, database, or cache.
Sessions
POST /login → server creates session → sets session_id cookie →
client sends cookie on every request → server looks up session →
returns user infoSessions are stored on the server (database, Redis). The only thing in the cookie is an ID.
JWT (JSON Web Tokens)
POST /login → server signs a JWT → client stores it →
client sends JWT in Authorization header → server verifies signature →
returns user infoJWTs are self-contained — the server doesn't need to look anything up. But they can't be revoked individually.
Caching Strategy
- Stale-while-revalidate: serve stale cache immediately, revalidate in background
- Cache invalidation: invalidate cache when the underlying data changes
- SWR (Stale-While-Revalidate): a popular React library implementing this pattern
Key Principle
Keep state in the closest place it's needed. If only one component needs it, it's local state. If many components need it, it's global state. If the server needs it, it's server state.