Nuxt 3 SSR Fails to Send Cookies During Page Reload on Protected Routes

Hello StackOverflow Community,

I am working on my first full-stack project using Nuxt 3 for the front end and Node.js (Express), and Sequelize for the back end. I am encountering an issue when reloading a protected route. The problem seems to be related to cookies not being correctly sent during SSR, which causes the backend to fail to authenticate the request.

The Issue

Whenever I reload a protected route, the cookies (containing the access token) are not correctly sent to the backend. This causes the backend to think that no access token is present, leading to multiple failed attempts and eventual redirection to the login page. This issue does not occur when navigating through the app using NuxtLink components; it only happens on a full page reload.

So I think this really might be due to Nuxt 3 not being able to access the cookies because of its SSR? I am quite lost on this one…

Error Logs

Backend Logs:

undefined
Verifying access token...
Received token: undefined
No access token found.
GET /users/me/favorite-genres took 0.001 seconds

undefined
Verifying access token...
Received token: undefined
No access token found.
GET /users/me/favorite-genres took 0.000 seconds

Frontend Logs:

fetching user data at path:  /users/me/favorite-genres
Refreshing tokens...

ERROR  Failed to fetch data: localStorage is not defined

Redirecting to login page...

My Setup

Nuxt 3 Configuration (nuxt.config.ts):

export default defineNuxtConfig({
  ssr: true,
  runtimeConfig: {
    public: {
      apiBaseUrl: process.env.API_BASE_URL || 'http://localhost:3000',
    },
  },
});

Axios Plugin (plugins/api.js):

import axios from 'axios';
import { useUserStore } from '~/composables/stores/user';
import { useRouter } from '#imports';

export default defineNuxtPlugin(nuxtApp => {
  const router = useRouter();
  const api = axios.create({
    baseURL: nuxtApp.$config.public.apiBaseUrl,
    withCredentials: true,
  });

  api.interceptors.response.use(
    response => response,
    async error => {
      const originalRequest = error.config;
      const userStore = useUserStore();

      if (error.response?.status === 401 && error.response.data?.refresh && !originalRequest._retry) {
        originalRequest._retry = true;
        try {
          const { data } = await api.post('/users/me/refresh');
          return api(originalRequest);
        } catch (err) {
          userStore.clearUser();
          await router.push('/login');
          return Promise.reject(err);
        }
      }

      if (error.response?.status === 403 && error.response.data?.redirectTo) {
        userStore.logout();
        userStore.clearUser();
        await router.push(error.response.data.redirectTo);
      }

      return Promise.reject(error);
    }
  );

  return {
    provide: {
      api,
    },
  };
});

User Service (composables/api/userService.js):

import { useNuxtApp } from '#app';

export function useUserService() {
  const { $api } = useNuxtApp();

  const fetchUserData = async (path) => {
    try {
      const response = await $api.get(path);
      return response.data;
    } catch (error) {
      throw error;
    }
  };

  const updateUserData = async (path, data) => {
    try {
      const response = await $api.patch(path, data);
      return response.data;
    } catch (error) {
      throw error;
    }
  };

  const updateUserAddress = async (data) => {
    try {
      const response = await $api.patch('/users/me/address', data);
      return response.data;
    } catch (error) {
      throw error;
    }
  };

  return {
    fetchUserData,
    updateUserData,
    updateUserAddress,
  };
}

What I’ve Tried

  1. Axios Interceptors: Ensured that the access token is attached to every request.
  2. Nuxt Middleware: Created middleware to verify and attach the token before each request.
  3. useCookie Composable: Used useCookie to manage cookies for SSR/CSR compatibility.

Despite these efforts, the issue persists, maybe i am setting those incorrectly… It appears that the server does not have access to the cookies during SSR, resulting in undefined tokens.

Question

How can I ensure that cookies are correctly sent during SSR in Nuxt 3, especially on page reloads for protected routes? Any guidance or suggestions would be highly appreciated.

Thank you!