I’m running into an issue with Nuxt 4 / Vue 3 where I need to redirect the user to /login immediately after logging them out or when a token expires. The problem is that if I call navigateTo('/login') while the current page is still rendering (for example, a heavy dashboard page), the app either blocks or crashes.
I’ve found that adding a setTimeout(() => navigateTo('/login'), 500) works, but it feels hacky and unreliable. I’ve also tried nextTick() and router.replace(), but on heavy pages it still fails.
Is there a more reliable way to force a redirect in Nuxt 4 that interrupts the current render or navigation safely, without reloading the whole PWA?
Code: (in a util function, called when pinia stores make requests)
await navigateTo('/login');
Full code:
export interface APIResult<Data> {
status: 'success' | 'error';
message: string;
result?: Data;
}
export interface FetchOptions {
id: string; // id of the ressource
}
function customFetch(route: string, method: string, body: object) {
const { API } = useApi();
if (route.endsWith('/')) route = route.slice(0, -1);
return fetch(`${API}/${route}`, {
method: method,
body: method === 'GET' || method === 'DELETE' ? null : body instanceof FormData ? body : JSON.stringify(body),
headers: body instanceof FormData ? {} : { 'Content-Type': 'application/json; charset=UTF-8' },
credentials: 'include',
});
}
let refreshPromise: Promise<void> | null = null;
/**
* Refresh the access token, ensuring only one refresh request occurs at a time.
* Other requests wait for this refresh.
*/
async function refreshAccessToken(): Promise<void> {
if (!refreshPromise) {
refreshPromise = (async () => {
console.log('[AUTH] Refreshing access token...');
const res = await customFetch('auth/refresh', 'POST', {});
const data = await res.json();
if (!res.ok || data.status !== 'success') {
console.warn('[AUTH] Refresh failed, logging out.');
useUserStore().post_logout();
await navigateTo('/login');
throw new Error('Refresh token invalid');
}
console.log('[AUTH] Access token refreshed.');
})();
// reset the promise when done
refreshPromise.finally(() => (refreshPromise = null));
}
return refreshPromise;
}
/**
* Main function: sends an API request,
* attempts a refresh if the token is expired,
* and retries the request once if needed.
*/
export async function makeRequest<T>(route: string, method: string, body: object): Promise<APIResult<T>> {
try {
const response = await customFetch(route, method, body);
const data = await response.json();
if (response.ok && data.status === 'success') {
return data;
}
// Invalid or expired token case -> try to refresh and retry
if (response.status === 401 && (data.message === 'Bad access token.' || data.message === 'Missing token cookies.')) {
try {
await refreshAccessToken();
// Retry the request once after refresh
const retry = await customFetch(route, method, body);
const retryData = await retry.json();
return retryData;
} catch {
return { status: 'error', message: 'Authentication failed.' };
}
}
return data;
} catch (err) {
console.error('[API ERROR]', err);
return { status: 'error', message: String(err) };
}
}
Currently, when I manually remove the cookies from the backend I have this error: (nothing in the console): chrome error