Direct answer
Make the Axios interceptor change a single source of truth in Pinia and trigger navigation once, and make Vue Router guards read that same Pinia flag synchronously. Do not let the interceptor and the guard each decide to redirect independently.
Immediate redirection on 401 without waiting for next navigation
Centralize logout in the Pinia auth store. The interceptor calls the store action, the store clears the token and sets isAuthenticated = false, then the store or a router plugin performs a single programmatic navigation to login.
- Use one Axios instance registered once at app bootstrap to avoid duplicate interceptor runs on hot reload.
- In the response interceptor, check for 401 on non-auth endpoints. Call
authStore.logout() once and set a flag sessionExpired.
- From the store action, use the router instance to
router.replace({ name: 'login', query: { expired: '1' } }). This is the only place that redirects.
- Skip handling for requests that are part of login or token refresh to avoid redirect loops.
Prevent flashes of protected content
Keep the guard reactive to the same Pinia flag and avoid API calls inside guards.
- Guard reads
authStore.isAuthenticated synchronously. If false, redirect to login immediately.
- Components for protected routes use
v-if="authStore.isAuthenticated" or a layout guard so rendering is blocked before mount.
- On logout, clear token from storage and Pinia state before navigation completes, so the guard sees the unauthenticated state on the next tick.
Confirmed facts
An Axios response interceptor can centrally inspect HTTP error status codes for authentication failures and trigger a single logout routine, preventing scattered error handling across components.
Vue Router navigation guards can gate access to protected routes by checking a shared authentication state before allowing navigation.
Likely explanation
When interceptor-driven redirects and router-guard redirects operate independently, double navigation, flicker, or missed cleanup can occur on session expiry.
Session expiration state should be held in a single reactive source, such as a Pinia auth store, so both the interceptor and router read the same flag.
Steps for this case
- Centralize logout in Pinia: clear token, set
isAuthenticated = false, optionally set sessionExpired = true.
- Interceptor calls
authStore.handle401() once per failure and does not redirect itself.
- Store action performs the single
router.replace to login.
- Router
beforeEach guard checks authStore.isAuthenticated and redirects if false.
- Ensure Axios instance is a singleton and interceptor is registered once.
Caution: Do not redirect inside the interceptor for requests that are part of the login or token refresh flow. Ensure router guards do not trigger API calls that themselves cause 401 handling, which can create circular redirects.
One diagnostic detail that changes the recommendation: Do you have an active refresh token flow that retries the original request on 401? If yes, the interceptor should attempt refresh first and only logout after refresh fails.