You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Nuxt与Apollo集成认证时遭遇Store重置错误及自定义客户端配置问题

Hey there, let's break down how to fix that frustrating invariant violation error and polish your custom Apollo client setup for your Nuxt project.

1. Fixing the "Store reset while query was in flight" Error

This error happens because when you trigger onLogout() (which resets the Apollo Store) for an expired token, there are still pending GraphQL requests in the link chain. The store reset conflicts with these in-flight requests, causing the invariant violation. Here's how to resolve it:

a. Cancel In-Flight Requests Before Logout

Instead of forwarding the operation when a 401 is detected, cancel the pending request first to avoid conflicting with the store reset:

import { onError } from '@apollo/client/link/error';

export default ({ app }: Context) => {
  const errorLink = onError(async ({ graphQLErrors, operation, forward }) => {
    if (graphQLErrors) {
      for (const err of graphQLErrors) {
        if (err?.extensions?.exception.status === 401) {
          // Mark the operation as skipped to cancel it
          operation.setContext({ skip: true });
          // Perform logout after canceling the request
          await app.$apolloHelpers.onLogout();
          // Redirect to login page (optional but recommended)
          app.router?.push('/login');
          break; // Stop processing other errors to avoid duplicate logout
        }
      }
    }
    // Only forward the operation if no 401 was handled
    return forward(operation);
  });

  // ... rest of your client config
}

b. Avoid Async/Await in graphQLErrors.map()

The map() method is synchronous, so using async inside it creates unresolved Promises that break the link chain. Use a for...of loop instead for proper async error handling.

2. Custom Apollo Client Configuration Optimizations

Your current setup replaces the default Apollo link entirely, which can break built-in features like HTTP headers or cache integration. Here's how to refine it:

Combine your error link with the default HTTP link instead of replacing it, so you don't lose core Apollo behavior:

import { createHttpLink } from '@apollo/client/core';
import { from } from '@apollo/client/link/core';
import { InMemoryCache } from '@apollo/client/core';

export default ({ app }: Context) => {
  // Create a standard HTTP link for GraphQL requests
  const httpLink = createHttpLink({
    uri: 'http://localhost:3001/graphql',
  });

  const errorLink = onError(async ({ graphQLErrors, networkError, operation, forward }) => {
    // ... your refined error handling logic here
  });

  return {
    // Chain errorLink before httpLink to catch errors early
    link: from([errorLink, httpLink]),
    // Reuse the existing cache or initialize a new one
    cache: app?.$apollo?.defaultClient?.cache || new InMemoryCache(),
    // Keep other default Nuxt Apollo configs (like auth headers) if needed
  };
}

b. Add Token Refresh Logic (If Needed)

Instead of immediately logging out on 401, you can attempt to refresh the token first and retry the request. Here's a simplified implementation:

// Inside your errorLink callback
if (err?.extensions?.exception.status === 401) {
  // Prevent concurrent refresh requests
  if (!app.$apolloHelpers.isRefreshing) {
    app.$apolloHelpers.isRefreshing = true;
    try {
      // Fetch new token using your refresh endpoint
      const newTokenData = await app.$axios.$post('/refresh-token', {
        refreshToken: app.$apolloHelpers.getRefreshToken(),
      });
      // Update auth state with new tokens
      app.$apolloHelpers.onLogin(newTokenData.token, newTokenData.refreshToken);
      // Update the current request's authorization header
      operation.setContext(({ headers = {} }) => ({
        headers: {
          ...headers,
          authorization: `Bearer ${newTokenData.token}`,
        },
      }));
      app.$apolloHelpers.isRefreshing = false;
      // Retry the original request
      return forward(operation);
    } catch (refreshErr) {
      // Refresh failed, proceed to logout
      app.$apolloHelpers.isRefreshing = false;
      await app.$apolloHelpers.onLogout();
      app.router?.push('/login');
      operation.setContext({ skip: true });
    }
  } else {
    // Wait for ongoing refresh to complete, then retry
    return new Promise((resolve) => {
      app.$apolloHelpers.onRefreshDone = () => {
        resolve(forward(operation));
      };
    });
  }
}

Note: You'll need to add isRefreshing and onRefreshDone state properties to your apolloHelpers utility to manage the refresh flow.

c. Improve Error Classification

Add specific handling for different GraphQL error codes to make debugging easier:

if (graphQLErrors) {
  for (const err of graphQLErrors) {
    switch (err.extensions?.code) {
      case 'UNAUTHENTICATED':
        // Handle 401 token expiry logic
        break;
      case 'FORBIDDEN':
        console.error('You don\'t have permission to perform this action');
        break;
      case 'VALIDATION_ERROR':
        console.error('Input validation failed:', err.message);
        break;
      default:
        console.error('GraphQL Error:', err.message);
    }
  }
}

d. Remove Type Casts

Avoid using errorLink as any by ensuring proper type imports. The onError function from @apollo/client/link/error already returns a valid Apollo Link type, so no casting is needed.

内容的提问来源于stack exchange,提问作者Antoni

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 22:49:08