Gatsby集成Apollo认证:登录后Token未更新导致状态不刷新问题
解决Gatsby+Apollo登录后无需刷新即可更新状态的问题
我之前在Gatsby集成Apollo的项目里也碰到过一模一样的坑!核心问题其实是你初始化ApolloClient时硬编码了Token,导致后续Token更新后,Client的请求头并没有同步更新——哪怕你把新Token存进了localStorage,后续查询依然带着初始化时的空Token,自然拿不到登录后的用户数据。下面是我亲测有效的解决方案:
1. 改造ApolloClient配置,动态获取Token
不要在初始化Client时直接传入固定的Authorization头,改用setContext拦截器,让每次请求都实时从localStorage读取最新Token:
// src/apollo/client.js (单独抽离配置文件更易维护) import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client'; import { setContext } from '@apollo/client/link/context'; // 创建基础HTTP链接 const httpLink = createHttpLink({ uri: '你的GraphQL接口地址', }); // 动态添加Auth头的拦截器 const authLink = setContext((_, { headers }) => { // 每次请求都从localStorage拿最新Token const token = localStorage.getItem('token'); return { headers: { ...headers, authorization: token ? `Bearer ${token}` : '', }, }; }); // 组装Client实例 export const client = new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache(), });
然后在gatsby-browser.js和gatsby-ssr.js里统一用这个Client包裹根元素:
// gatsby-browser.js & gatsby-ssr.js import { ApolloProvider } from '@apollo/client/react'; import { client } from './src/apollo/client'; export const wrapRootElement = ({ element }) => { return <ApolloProvider client={client}>{element}</ApolloProvider>; };
2. 优化登录Mutation:更新缓存+强制重查
光存Token还不够,你可以直接把登录返回的用户数据写入Apollo缓存,或者确保refetchQueries能拿到最新Token的请求:
import { Mutation } from '@apollo/client/react/components'; import { LOGIN_MUTATION, CURRENT_USER_QUERY } from './your-queries'; const LoginForm = () => { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); return ( <Mutation mutation={LOGIN_MUTATION} variables={{ email, password }} // 重查用户状态 refetchQueries={[{ query: CURRENT_USER_QUERY }]} onCompleted={(data) => { // 存Token到本地 localStorage.setItem('token', data.login.token); // 直接把用户数据写入缓存,比refetch更即时 client.writeQuery({ query: CURRENT_USER_QUERY, data: { currentUser: data.login.user }, }); }} > {(login, { loading, error }) => ( <form onSubmit={(e) => { e.preventDefault(); login(); }}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="邮箱" /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" /> <button type="submit" disabled={loading}> {loading ? '登录中...' : '登录'} </button> {error && <p className="error">{error.message}</p>} </form> )} </Mutation> ); };
这里的writeQuery是关键——直接把登录返回的用户数据写入缓存,你的状态组件会立刻感知到缓存变化,无需等待网络请求。
3. 调整状态组件的查询策略
确保状态组件的Query配置能正确响应缓存变化,比如用cache-and-network兼顾缓存速度和数据新鲜度:
import { Query } from '@apollo/client/react/components'; import { CURRENT_USER_QUERY } from './your-queries'; const UserStatus = () => { return ( <Query query={CURRENT_USER_QUERY} fetchPolicy="cache-and-network" // 优先用缓存,同时后台更新 > {({ data, loading }) => { if (loading) return <p>加载中...</p>; if (!data.currentUser) return <a href="/login">请登录</a>; return <p>欢迎回来,{data.currentUser.nickname}!</p>; }} </Query> ); };
为什么之前的方案没用?
- 你之前的ApolloClient初始化时Token是空的,后续Token更新后Client没有重新读取,导致请求头一直是空的;
refetchQueries本身没问题,但如果请求头没更新,重查依然会用旧Token,自然拿不到正确数据;- Gatsby的静态渲染特性可能导致组件提前执行,但只要用
wrapRootElement正确包裹ApolloProvider,再配合动态Token拦截器,就能避免这个问题。
按上面的步骤调整后,登录成功后状态组件会立刻更新,完全不需要刷新页面!
内容的提问来源于stack exchange,提问作者Lionel T
相关产品推荐
相关产品推荐

