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

Next.js报Cannot read property 'useContext' of null错误解决

问题答案

先给明确结论:

  • React Context的值根本不支持在函数组件/自定义Hook外部访问。useContext是React Hook的一种,有硬性调用要求:只能在React函数组件的顶层作用域、或者自定义Hook内部调用。你在模块顶层、普通工具函数、组件渲染树之外调用的时候,React找不到对应的组件实例上下文,就会抛出你碰到的TypeError: Cannot read property 'useContext' of null错误。
  • 你现在的代码有两个核心问题:一是在模块最外层直接非法调用useContext;二是withApollo初始化ApolloClient的时机比App组件渲染早,这时候包裹内容的<Provider>还没挂载,就算把useContext挪到withApollo的回调函数里,也拿不到Context里存的值。
  • 没必要硬凑在组件外面读Context,用Apollo自带的请求链路拦截器动态给请求加header,是生产环境最通用的实现方式,稳定性更高。
推荐实现代码

直接修改_app.js文件即可,其他业务逻辑不用动:

import "../styles/globals.css";
import Layout from "../components/Layout";
import { Provider, AppContext } from "../context/AppContext";
import Cookies from "js-cookie";
import withApollo from "next-with-apollo";
import { BACKEND_URL } from "../helpers";
import React, { useContext } from "react";
import {
  ApolloClient,
  ApolloProvider,
  InMemoryCache,
  createHttpLink,
  from
} from "@apollo/client";
import { setContext } from "@apollo/client/link/context";

// 基础GraphQL请求链路
const httpLink = createHttpLink({
  uri: `${BACKEND_URL}/graphql`,
});

// 鉴权拦截器:每个请求发出前动态往header里塞鉴权信息
const authLink = setContext((_, { headers }) => {
  // 直接从Cookie读token就行,你Context里存的user信息本来就是和Cookie同步的,直接读原始数据源更稳定,没必要绕一层Context传值
  const token = Cookies.get("your_token_cookie_key"); // 替换成你项目里实际存token的Cookie键名
  return {
    headers: {
      ...headers,
      authorization: token ? `Bearer ${token}` : "",
    },
  };
});

function App({ Component, pageProps, apollo }) {
  // 组件内部可以正常调用useContext拿user,不影响你之前的业务使用
  const { user } = useContext(AppContext);
  console.log("组件内正常获取user:", user);
  return (
    <ApolloProvider client={apollo}>
      <Provider>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </Provider>
    </ApolloProvider>
  );
}

export default withApollo(({ initialState }) => {
  return new ApolloClient({
    link: from([authLink, httpLink]),
    cache: new InMemoryCache().restore(initialState || {}),
  });
})(App);
可选轻量方案(不推荐生产环境用)

如果你一定要从Context里读user来配置请求头,可以把ApolloClient实例挪到App组件内部,用useMemo包裹创建,保证useContext是在组件顶层合法调用,示例结构:

import { useMemo } from "react";

function App({ Component, pageProps }) {
  const { user } = useContext(AppContext);
  // 组件内部创建Apollo实例,这时候可以正常拿到Context值
  const apolloClient = useMemo(() => {
    return new ApolloClient({
      uri: `${BACKEND_URL}/graphql`,
      cache: new InMemoryCache(),
      headers: user?.token ? {
        authorization: `Bearer ${user.token}`
      } : {}
    })
  }, [user])

  return (
    <Provider>
      <ApolloProvider client={apolloClient}>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </ApolloProvider>
    </Provider>
  )
}

export default App;

注意:这个方案有明显缺陷,user一旦变化就会重建整个ApolloClient实例,清空所有已经缓存的GraphQL请求数据,会额外增加不必要的请求开销,只适合小项目临时快速实现用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 02:18:03