I'm reconfiguring my NextJS/Apollo app to allow for SSG with GraphQL API routes, and I'm using this official NextJS starter example as a base for the client config.
I've run into an interesting issue though in my own app, so I've went back to starter example and tried to reproduce it, and was able to. The issue is that without any context object passed into the query resolvers, everything works fine (in the playground and on the client). However, when you introduce a context object and pass it to the resolvers, it works fine in the playground but the context object is undefined
when fired from the client. This is the code from the official NextJS starter example, I'll comment where I've added anything.
graphql.js
import { ApolloServer } from "apollo-server-micro";
import { schema } from "../../apollo/schema";
const apolloServer = new ApolloServer({
schema,
context: { //
foo: "bar", // this is the context object I've added
}, //
});
export const config = {
api: {
bodyParser: false,
},
};
export default apolloServer.createHandler({ path: "/api/graphql" });
typedefs.js
import { gql } from '@apollo/client'
export const typeDefs = gql`
type User {
id: ID!
name: String!
status: String!
}
type Query {
viewer: User
}
`
schema.js
import { makeExecutableSchema } from 'graphql-tools'
import { typeDefs } from './type-defs'
import { resolvers } from './resolvers'
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
resolvers.js
export const resolvers = {
Query: {
viewer: (_parent, _args, context, _info) => {
console.log("context", context); // console log check that I've added
return { id: 1, name: "John Smith", status: "cached" };
},
},
};
When I run this in the GraphQL playground and query the API, it gives me the correct response, and in my terminal console it returns the context foo: bar
object from the console log, so in the server the context object is being passed correctly. However, when I visit the index page in the browser, which is this:
index.js
import gql from "graphql-tag";
import Link from "next/link";
import { useQuery } from "@apollo/client";
import { initializeApollo } from "../apollo/client";
const ViewerQuery = gql`
query ViewerQuery {
viewer {
id
name
status
}
}
`;
const Index = () => {
const {
data: { viewer },
} = useQuery(ViewerQuery);
return (
<div>
You're signed in as {viewer.name} and you're {viewer.status} goto{" "}
<Link href="/about">
<a>static</a>
</Link>{" "}
page.
</div>
);
};
export async function getStaticProps() {
const apolloClient = initializeApollo();
await apolloClient.query({
query: ViewerQuery,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
},
};
}
export default Index;
...the viewer name and viewer status are rendered, so the query is actually happening, but in the console, the context
object console log is returning undefined
. So when used in the client, the context
is being lost somehow. I find this interesting, since this is an official NextJS starter example, and unless they've set up the client to not accept context in the resolvers, I can't see what the problem is. And if it is the case that the client is not set up to accept context, is there any other official examples with a client setup that does?
This is a long question now, but here is the client.js
setup:
import { useMemo } from "react";
import { ApolloClient, InMemoryCache } from "@apollo/client";
let apolloClient;
function createIsomorphLink() {
if (typeof window === "undefined") {
const { SchemaLink } = require("@apollo/client/link/schema");
const { schema } = require("./schema");
return new SchemaLink({ schema });
} else {
const { HttpLink } = require("@apollo/client/link/http");
return new HttpLink({
uri: "http://localhost:3000/api/graphql",
credentials: "same-origin",
});
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: createIsomorphLink(),
cache: new InMemoryCache(),
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === "undefined") return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}
I implore anyone to clone this official repo and see if they can figure out how to get context working in the client, or if anyone knows why this client setup isn't working for context and knows a client setup that does accept resolver context, I would appreciate it. This problem has cost me two days now!
I've figured out the problem. The client configuration is using SchemaLink
for the http request, and the context is passed in the SchemaLink
constructor function, not in the server options, because context is passed in the http headers with httpLink.