-1

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

1 Answer 1

0

One clean way should be

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();
        
        // ✅ Interrupt navigation and redirect
        if (process.client) {
          // Stop current navigation/render
          abortNavigation();
          // Force immediate redirect
          await navigateTo('/login', { 
            replace: true,
            external: false // Important for PWA
          });
        }
        
        throw new Error('Refresh token invalid');
      }
      console.log('[AUTH] Access token refreshed.');
    })();
    
    refreshPromise.finally(() => (refreshPromise = null));
  }
  return refreshPromise;
}

becaues navigateto() is async and can create issues when other async operations are running.

If you create a Stackblitz example for me I'll have a look to it. Otherwise it is hard to reproduce.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.