A Token's Lifecycle: The Complete JWT Login Loop in React
The four-step loop—sign, store, carry, verify—is the skeleton of nearly every frontend auth system. Understanding how the axios instance, interceptors, state store, and route guard connect eliminates the confusion that comes from reading about each piece in isolation.
A full-stack JWT login flow built with React 19, zustand, axios, and react-router-dom 7, using vite-plugin-mock to simulate the backend. The token's journey starts at a login form, passes through a mock server that signs it with jsonwebtoken, lands in localStorage and a zustand store, and then rides along on every request via an axios request interceptor. A route guard component checks the store and redirects unauthenticated users.
The architecture centers on a single axios instance configured with a base URL and two interceptors: one that reads the token from localStorage and injects it into the Authorization header, and another that strips the response data wrapper. Business API modules import this instance, not the raw axios library, which is a common pitfall that causes `instance is not defined` errors.
Logout clears both localStorage and the zustand store, instantly updating the UI and reactivating the route guard. The mock server mirrors a real backend by signing tokens at `/api/login` and verifying them at `/api/repo`, creating a self-contained development loop that requires no external API.
The `instance is not defined` bug is a symptom of a deeper problem: developers often treat axios interceptors as magic rather than understanding that the configured instance is a distinct object that must be explicitly imported.
Mocking the backend inside the frontend toolchain turns JWT from a theoretical concept into a concrete, testable loop. The symmetry between `jwt.sign` at `/api/login` and `jwt.verify` at `/api/repo` makes the stateless nature of JWT tangible.
pnpm 10+ ignoring build scripts by default is a sharp edge that will trip up developers who expect `pnpm install` to behave like npm. The esbuild native binary requirement means a silent failure that manifests as a cryptic Vite 500 error.