Closed saostad closed 1 year ago
Here's my modification of the official nextjs example, which passing the cookie from nextjs ctx to fetch on ssr.
import React from "react"
import Head from "next/head"
import { ApolloProvider } from "@apollo/react-hooks"
import { ApolloClient } from "apollo-client"
import { InMemoryCache } from "apollo-cache-inmemory"
import { HttpLink } from "apollo-link-http"
import fetch from "isomorphic-unfetch"
let apolloClient = null
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
* @param {Function|Class} PageComponent
* @param {Object} [config]
* @param {Boolean} [config.ssr=true]
*/
export function withApollo(PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(apolloState)
return (
<ApolloProvider client={client} >
<PageComponent {...pageProps} />
</ApolloProvider>
)
}
// Set the correct displayName in development
if (process.env.NODE_ENV !== "production") {
const displayName =
PageComponent.displayName || PageComponent.name || "Component"
if (displayName === "App") {
console.warn("This withApollo HOC only works with PageComponents.")
}
WithApollo.displayName = `withApollo(${displayName})`
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async ctx => {
const { AppTree } = ctx
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApolloClient({}, ctx.req.headers.cookie))
// Run wrapped getInitialProps methods
let pageProps = {}
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx)
}
// Only on the server:
if (typeof window === "undefined") {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import("@apollo/react-ssr")
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient
}}
/>
)
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error("Error while running `getDataFromTree`", error)
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
}
}
// Extract query data from the Apollo store
// @ts-ignore
const apolloState = apolloClient.cache.extract()
return {
...pageProps,
apolloState
}
}
}
return WithApollo
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {Object} initialState
*/
function initApolloClient(initialState = {}, cookie = "") {
// Make sure to create a new client for every server-side request so that data
// isn"t shared between connections (which would be bad)
if (typeof window === "undefined") {
return createApolloClient(initialState, cookie)
}
// Reuse client on the client-side
if (!apolloClient) {
// @ts-ignore
apolloClient = createApolloClient(initialState)
}
return apolloClient
}
/**
* Creates and configures the ApolloClient
* @param {Object} [initialState={}]
*/
function createApolloClient(initialState = {}, cookie) {
const enchancedFetch = (url, init) => {
return fetch(url, {
...init,
headers: {
...init.headers,
"Cookie": cookie
}
}).then(response => response)
}
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
return new ApolloClient({
ssrMode: typeof window === "undefined", // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
uri: "http://localhost:3000/api", // Server URL (must be absolute)
credentials: "same-origin", // Additional fetch() options like `credentials` or `headers`
fetch: enchancedFetch
}),
cache: new InMemoryCache().restore(initialState)
})
}
I had the same issue with Next.Js. Tried to resolve it and no way, started feeling something was wrong with apollo, till I tried it against FusionJs and it just works
my problem was because of the bad configuration of CORS on my server. after I configured it correctly it works fine. My guess is the first request is from browser and that's why it does not include any authentication header info.
Hey did you manage to fix this problem? I'm having the same issue. I'm using graphql yoga as my server. This works fine in localhost but in production the cookie is not being sent in ssr. btw I have Configured SSL in my server maybe that has something to do with this.
Can you specify what exact Cors settings on the server did you set up? I'm having the same exact issue for over a month. Works in localhost, not after deploy. Tried a bazillion cors settings.
Can you specify what exact Cors settings on the server did you set up? I'm having the same exact issue for over a month. Works in localhost, not after deploy. Tried a bazillion cors settings.
what is your server?
Omg after struggling with this issue for more than a month, I finally found the solution. The cors settings where fine. But in my mutation when setting the cookie you have to specifically set in res.cookie
:
{domain: '.yourdomain.com'}
Only then will the cookie persist during refresh when backend is at backend.yourdomain.com and frontend at frontend.yourdomain.com. It was working on localhost because it's the same domain: localhost
Hi @chemicalkosek I got the same issue since one week and I don't understand.. Can you tell me what CORS settings you're using ? I tried to add the domain in the cookie but still does not work.. :/
This is how i set up cookie
context.response.cookie("token", token, { domain: ".whos.now.sh", secure: true, httpOnly: true, maxAge: 1000 * 60 * 60 * 24 * 365 // 1 year });
that's the CORS configuration
cors: { credentials: true, origin: process.env.fronturl, }
Not sure, if you're using secure: true
then you need to have ssl ('https'). Do you have that?
Also I'm not sure if you can set cookies on now.sh
Source: https://twitter.com/wesbos/status/1062776904598650880
Yes I use https. Well, here is the problem probably ! I will try tomorrow ! A big thank you for your answer, I let you know
Here's my modification of the official nextjs example, which passing the cookie from nextjs ctx to fetch on ssr.
import React from "react" import Head from "next/head" import { ApolloProvider } from "@apollo/react-hooks" import { ApolloClient } from "apollo-client" import { InMemoryCache } from "apollo-cache-inmemory" import { HttpLink } from "apollo-link-http" import fetch from "isomorphic-unfetch" let apolloClient = null /** * Creates and provides the apolloContext * to a next.js PageTree. Use it by wrapping * your PageComponent via HOC pattern. * @param {Function|Class} PageComponent * @param {Object} [config] * @param {Boolean} [config.ssr=true] */ export function withApollo(PageComponent, { ssr = true } = {}) { const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => { const client = apolloClient || initApolloClient(apolloState) return ( <ApolloProvider client={client} > <PageComponent {...pageProps} /> </ApolloProvider> ) } // Set the correct displayName in development if (process.env.NODE_ENV !== "production") { const displayName = PageComponent.displayName || PageComponent.name || "Component" if (displayName === "App") { console.warn("This withApollo HOC only works with PageComponents.") } WithApollo.displayName = `withApollo(${displayName})` } if (ssr || PageComponent.getInitialProps) { WithApollo.getInitialProps = async ctx => { const { AppTree } = ctx // Initialize ApolloClient, add it to the ctx object so // we can use it in `PageComponent.getInitialProp`. const apolloClient = (ctx.apolloClient = initApolloClient({}, ctx.req.headers.cookie)) // Run wrapped getInitialProps methods let pageProps = {} if (PageComponent.getInitialProps) { pageProps = await PageComponent.getInitialProps(ctx) } // Only on the server: if (typeof window === "undefined") { // When redirecting, the response is finished. // No point in continuing to render if (ctx.res && ctx.res.finished) { return pageProps } // Only if ssr is enabled if (ssr) { try { // Run all GraphQL queries const { getDataFromTree } = await import("@apollo/react-ssr") await getDataFromTree( <AppTree pageProps={{ ...pageProps, apolloClient }} /> ) } catch (error) { // Prevent Apollo Client GraphQL errors from crashing SSR. // Handle them in components via the data.error prop: // https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error console.error("Error while running `getDataFromTree`", error) } // getDataFromTree does not call componentWillUnmount // head side effect therefore need to be cleared manually Head.rewind() } } // Extract query data from the Apollo store // @ts-ignore const apolloState = apolloClient.cache.extract() return { ...pageProps, apolloState } } } return WithApollo } /** * Always creates a new apollo client on the server * Creates or reuses apollo client in the browser. * @param {Object} initialState */ function initApolloClient(initialState = {}, cookie = "") { // Make sure to create a new client for every server-side request so that data // isn"t shared between connections (which would be bad) if (typeof window === "undefined") { return createApolloClient(initialState, cookie) } // Reuse client on the client-side if (!apolloClient) { // @ts-ignore apolloClient = createApolloClient(initialState) } return apolloClient } /** * Creates and configures the ApolloClient * @param {Object} [initialState={}] */ function createApolloClient(initialState = {}, cookie) { const enchancedFetch = (url, init) => { return fetch(url, { ...init, headers: { ...init.headers, "Cookie": cookie } }).then(response => response) } // Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient return new ApolloClient({ ssrMode: typeof window === "undefined", // Disables forceFetch on the server (so queries are only run once) link: new HttpLink({ uri: "http://localhost:3000/api", // Server URL (must be absolute) credentials: "same-origin", // Additional fetch() options like `credentials` or `headers` fetch: enchancedFetch }), cache: new InMemoryCache().restore(initialState) }) }
I see you are getting cookie via ctx.req.headers.cookie, can you also please show some example how in component you can set cookie to be accessible like this? I've just tried to use kind of setting cookie, but then the cookie is just available after refresh / swith between sites. So, therefore immeditaly after I do log-in it does not set header with cookie, but after you refresh or switch site (when apollo is reinitalized).
I used for example:
const { myCookie } = cookies(ctx); (trying to get cookie), but as I said it was not available (also not in setContext).
Is there any solution?
@nadzic There are no headers on the client side, so you can't access them. You need to somehow use document.cookie
or maybe some other package like universal-cookie or react-cookie
The newest nextjs example works with a seperate apolloClient.js file. Modified it like so (based on mzygmunt's code).
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import fetch from 'isomorphic-unfetch';
import { endpoint } from '../config';
export default function createApolloClient(initialState, ctx) {
// The `ctx` (NextPageContext) will only be present on the server.
// use it to extract auth headers (ctx.req) or similar.
const enchancedFetch = (url, init) =>
fetch(url, {
...init,
headers: {
...init.headers,
Cookie: ctx.req.headers.cookie,
},
}).then(response => response);
return new ApolloClient({
ssrMode: Boolean(ctx),
link: new HttpLink({
uri: endpoint, // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
fetch: ctx ? enchancedFetch : fetch,
}),
cache: new InMemoryCache().restore(initialState),
});
}
@derozan10 Can you point which nextjs example are you referring to.
@derozan10 Can you point which nextjs example are you referring to.
@derozan10 Can you point which nextjs example are you referring to.
Thanks!
Does anybody know if this already works out of the box with https://github.com/lfades/next-with-apollo, i have forwarded cookie headers explicitly but still seems SSR doesn't behave correctly
import { ApolloProvider } from '@apollo/react-hooks';
import withApollo from 'next-with-apollo';
import ApolloClient, { InMemoryCache } from 'apollo-boost';
export default withApollo(
({ initialState, headers }: any) => {
return new ApolloClient({
credentials: 'include',
uri: 'https://www.my-api.com',
headers: {
cookie: headers?.cookie,
},
cache: new InMemoryCache().restore(initialState || {}),
});
},
{
render: ({ Page, props }) => {
const { apollo } = props;
return (
<ApolloProvider client={apollo}>
<Page {...props} />
</ApolloProvider>
);
},
},
);
@mjurincic You'll need to pass getDataFromTree
in next-with-apollo, read the docs:
// import { getDataFromTree } from '@apollo/react-ssr';
// You can also override the configs for withApollo here, so if you want
// this page to have SSR (and to be a lambda) for SEO purposes and remove
// the loading state, uncomment the import at the beginning and this:
//
// export default withApollo(Index, { getDataFromTree });
Thanks,
I didn't mention I'm already passing getDataFromTree
it may be that err is somewhere else, I'll update ticket if I figure it out.
Hi @mjurincic: did you figure out the issue?
The newest nextjs example works with a seperate apolloClient.js file. Modified it like so (based on mzygmunt's code).
@derozan10 's and @mzygmunt code works with NextJs + Graphql Yoga + Prisma https://github.com/apollographql/apollo-client/issues/5089#issuecomment-598671071
I also changed my CORS options on the backend
...
const cors = require("cors");
const server = createServer();
var corsOptions = {
origin: process.env.FRONTEND_URL,
credentials: true
};
server.express.use(cors(corsOptions));
...
I also updated the dependencies until I could reach a 'no yarn warning' state
"dependencies": {
"@apollo/react-hooks": "^3.1.5",
"@apollo/react-ssr": "^3.1.5",
"@babel/core": "^7.1.2",
"@types/react": "^16.8.0",
"apollo-cache-inmemory": "^1.6.6",
"apollo-client": "^2.6.9",
"apollo-link-http": "^1.5.17",
"apollo-utilities": "^1.3.2",
"graphql": "14.3.1",
"graphql-tag": "^2.10.3",
"isomorphic-unfetch": "^3.0.0",
"next": "latest",
"prop-types": "^15.6.2",
"react": "^16.13.1",
"react-dom": "^16.13.1"
},
"devDependencies": {
"babel-plugin-graphql-tag": "^2.5.0"
}
I've tried the withapollo examples here with a separate createApollo and without. Either way I get headers undefined on Router.push
which is what I use after a successful login. I think it has something to do with being server side or not because if I refresh the home page, everything is fine. I am new to this and I'd greatly appreciate your help. Tried credentials: include
as well.
const [loginUser, { loading }] = useMutation(LOGIN_USER, {
errorPolicy: 'all',
onCompleted: (res) => {
if (res.login) {
Router.push('/');
} else {
setState((prevState) => ({
...prevState,
loginError: true,
}));
}
},
});
.
.
.
export default withApollo(Login);
@ODelibalta what does the ctx
shows?
I changed the line
if (ssr || PageComponent.getInitialProps) {
to
if (typeof window === "undefined" || PageComponent.getInitialProps) {
and it works. Here is the full withApollo.js
file for reference
import React from "react";
import Head from "next/head";
import { ApolloProvider } from "@apollo/react-hooks";
import createApolloClient from "./create";
let apolloClient = null;
/**
* Creates and provides the apolloContext
* to a next.js PageTree. Use it by wrapping
* your PageComponent via HOC pattern.
* @param {Function|Class} PageComponent
* @param {Object} [config]
* @param {Boolean} [config.ssr=true]
*/
export function withApollo(PageComponent, { ssr = true } = {}) {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
const client = apolloClient || initApolloClient(apolloState);
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
// Set the correct displayName in development
if (process.env.NODE_ENV !== "production") {
const displayName =
PageComponent.displayName || PageComponent.name || "Component";
if (displayName === "App") {
console.warn("This withApollo HOC only works with PageComponents.");
}
WithApollo.displayName = `withApollo(${displayName})`;
}
if (typeof window === "undefined" || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async (ctx) => {
const { AppTree } = ctx;
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = await (ctx.apolloClient = initApolloClient(
{},
ctx.req.headers.cookie
));
// Run wrapped getInitialProps methods
let pageProps = {};
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx);
}
// Only on the server:
if (typeof window === "undefined") {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps;
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import("@apollo/react-ssr");
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient,
}}
/>
);
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error("Error while running `getDataFromTree`", error);
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind();
}
}
// Extract query data from the Apollo store
// @ts-ignore
const apolloState = apolloClient.cache.extract();
return {
...pageProps,
apolloState,
};
};
}
return WithApollo;
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {Object} initialState
*/
function initApolloClient(initialState = {}, cookie = "") {
// Make sure to create a new client for every server-side request so that data
// isn"t shared between connections (which would be bad)
if (typeof window === "undefined") {
return createApolloClient(initialState, cookie)
}
// Reuse client on the client-side
if (!apolloClient) {
// @ts-ignore
apolloClient = createApolloClient(initialState)
}
return apolloClient
}
and the create.js
file
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { createHttpLink } from 'apollo-link-http';
import { setContext } from 'apollo-link-context';
import Cookies from 'js-cookie';
import { serverUrl } from '../config';
export default function createApolloClient(initialState, ctx) {
// The `ctx` (NextPageContext) will only be present on the server.
// use it to extract auth headers (ctx.req) or similar.
const authLink = setContext((_, { headers }) => {
// get the authentication token from local storage if it exists
const token = Cookies.get("XSRF-TOKEN");
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
"Access-Control-Allow-Credentials": true,
...(token ? { "X-CSRF-TOKEN": `${token}` } : {}),
...(token ? { "authorization": `Bearer ${token}` } : {}),
},
};
});
const httpLink = createHttpLink({
uri: serverUrl,
credentials: 'same-origin',
});
return new ApolloClient({
ssrMode: Boolean(ctx),
link: authLink.concat(httpLink),
connectToDevTools: true,
cache: new InMemoryCache().restore(initialState),
});
}
Here's my modification of the official nextjs example, which passing the cookie from nextjs ctx to fetch on ssr.
... export function withApollo(PageComponent, { ssr = true } = {}) { const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => { const client = apolloClient || initApolloClient(apolloState) return ( <ApolloProvider client={client} > <PageComponent {...pageProps} /> </ApolloProvider> ) } // Set the correct displayName in development if (process.env.NODE_ENV !== "production") { const displayName = PageComponent.displayName || PageComponent.name || "Component" if (displayName === "App") { console.warn("This withApollo HOC only works with PageComponents.") } WithApollo.displayName = `withApollo(${displayName})` } if (ssr || PageComponent.getInitialProps) { WithApollo.getInitialProps = async ctx => { const { AppTree } = ctx .....
@mzygmunt How do you combine this with getServersideProps
? I need to pull the context, and I get an error Error: You can not use getInitialProps with getServerSideProps. Please remove getInitialProps.
export async function getServerSideProps(context) {
const myVars = {
orderId: context["params"]["order"]
}
return {
props: {
myVars: myVars,
},
}
}
I successfully solved this problem in nextjs, it is actually very simple, just need to pass ctx in the getServerSideProps
of the page component.
pages/_app.js
import {Provider} from 'react-redux'
import {ApolloProvider} from '@apollo/react-hooks'
import {useApollo} from '../libs/apolloClient'
import '../asserts/global.css'
import store from '../redux/store'
const App = ({Component, pageProps}) => {
const apolloClient = useApollo(pageProps.initialApolloState)
return (
<Provider store={store}>
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
</Provider>
)
}
export default App
../libs/apolloClient.js
import {useMemo} from 'react'
import {getCookie} from "./cookie";
import {ApolloClient, createHttpLink, InMemoryCache} from '@apollo/client';
import {setContext} from '@apollo/client/link/context';
let apolloClient
function createApolloClient(ctx) {
let headers = {}
let accessToken=""
if (ctx) {
accessToken = getCookie('accessToken',ctx.req);
if (accessToken) {
headers = {
Authorization: accessToken,
}
}
}
const httpLink = createHttpLink({
uri: 'http://0.0.0.0:9900/query',
});
const authLink = setContext((req, {headers}) => {
// console.log(req, headers)
// get the authentication token from local storage if it exists
// return the headers to the context so httpLink can read them
return {
headers: {
...headers,
authorization: accessToken ? `Bearer ${accessToken}` : "",
}
}
});
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
credentials: 'include',
headers: headers
// defaultOptions: defaultOptions
})
}
export function initializeApollo(initialState = null, ctx) {
const _apolloClient = apolloClient ?? createApolloClient(ctx)
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// get 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, ctx) {
const store = useMemo(() => initializeApollo(initialState, ctx), [initialState, ctx])
return store
}
pages/home.js
import {initializeApollo} from '../libs/apolloClient'
import {articleQuery, recommendRelatedArticlesQuery} from '../gql/article.query'
import {useRouter} from 'next/router';
import {useApolloClient} from "@apollo/react-hooks";
const Home = ({initialApolloState}) => {
const router = useRouter();
const apolloClient = useApolloClient()
const {recommendRelatedArticles} = apolloClient.restore(initialApolloState).readQuery(
{
query: recommendRelatedArticlesQuery,
variables: {
articleId: "1nGtGEH6En",
page: 1,
pageSize: 5,
}
}, false
)
const {article} = apolloClient.restore(initialApolloState).readQuery(
{
query: articleQuery,
variables: {
sid: "1nGtGEH6En",
}
}, false
)
console.log(recommendRelatedArticles,article)
return (
<>
</>
)
}
export async function getServerSideProps(context) {
const apolloClient = initializeApollo(null, context)
await apolloClient.query({
query: articleQuery,
variables: {
sid: "1nGtGEH6En",
}
}).then(result => {
}).catch(e => {
console.log(e.message)
});
await apolloClient.query({
query: recommendRelatedArticlesQuery,
variables: {
articleId: "1nGtGEH6En",
page: 1,
pageSize: 5,
}
}).then(result => {
}).catch(e => {
console.log(e.message)
});
return {
props: {
initialApolloState: apolloClient.cache.extract(true),
},
}
}
export default Home
@codingcn Your config almost worked for me. I just couldn't make it work for SSR but it definitely works with Client side for me on production. Here is a simple version I did: https://codesandbox.io/s/goofy-johnson-sp8kb?file=/pages/index.tsx One thing to note that when I don't get issue when I run on localhost. How did you get the cookie on server-side? It turns out I can't get the cookie on server side (getServersideProps context) at all, but on client side I get the cookie and everything works just fine.
@codingcn @shoaibsharif My implementation is also the exact same. Curious about one difference I noticed with useApollo
hook:
export function useApollo(initialState, ctx) {
const store = useMemo(() => initializeApollo(initialState, ctx), [initialState, ctx])
return store
}
Pretty sure you can remove ctx param here as context is only passed when initializeApollo
is call from getServerSideProps
. The only relevant data here is hydrated server side (from the page component) and then passed to the hook via page props (in _app.tsx
). No context necessary.
Those changes are working at my side to send the cookie
from NextJS Server Side Rendering
to the Server (I'm using NestJS)
The full withApollo.tsx
import React from 'react';
import App from 'next/app';
import Head from 'next/head';
import { ApolloProvider } from '@apollo/react-hooks';
import createApolloClient from './apolloClient';
// On the client, we store the Apollo Client in the following variable.
// This prevents the client from reinitializing between page transitions.
let globalApolloClient = null;
/**
* Installs the Apollo Client on NextPageContext
* or NextAppContext. Useful if you want to use apolloClient
* inside getStaticProps, getStaticPaths or getServerSideProps
* @param {NextPageContext | NextAppContext} ctx
*/
export const initOnContext = (ctx) => {
const inAppContext = Boolean(ctx.ctx);
// We consider installing `withApollo({ ssr: true })` on global App level
// as antipattern since it disables project wide Automatic Static Optimization.
if (process.env.NODE_ENV === 'development') {
if (inAppContext) {
console.warn(
'Warning: You have opted-out of Automatic Static Optimization due to `withApollo` in `pages/_app`.\n' +
'Read more: https://err.sh/next.js/opt-out-auto-static-optimization\n'
);
}
}
// Initialize ApolloClient if not already done
const apolloClient =
ctx.apolloClient ||
initApolloClient(ctx.apolloState || {}, inAppContext ? ctx.ctx : ctx);
// We send the Apollo Client as a prop to the component to avoid calling initApollo() twice in the server.
// Otherwise, the component would have to call initApollo() again but this
// time without the context. Once that happens, the following code will make sure we send
// the prop as `null` to the browser.
apolloClient.toJSON = () => null;
// Add apolloClient to NextPageContext & NextAppContext.
// This allows us to consume the apolloClient inside our
// custom `getInitialProps({ apolloClient })`.
ctx.apolloClient = apolloClient;
if (inAppContext) {
ctx.ctx.apolloClient = apolloClient;
}
return ctx;
};
async function getHeaders(ctx) {
// if (typeof window !== 'undefined') return null
// if (typeof ctx.req === 'undefined') return null
// const s = await auth0.getSession(ctx.req)
// if (s && s.accessToken == null) return null
// return {
// authorization: `Bearer ${s ? s.accessToken: ''}`
// }
console.log('ooo', ctx?.req?.cookies)
if (ctx?.req?.cookies) {
const cookieItems = []
for (let key of Object.keys(ctx?.req?.cookies)) {
cookieItems.push(`${key}=${ctx.req.cookies[key]}`)
}
return {
cookie: cookieItems.join('; ')
}
}
return {
}
}
/**
* Always creates a new apollo client on the server
* Creates or reuses apollo client in the browser.
* @param {NormalizedCacheObject} initialState
* @param {NextPageContext} ctx
*/
const initApolloClient = (initialState, headers) => {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return createApolloClient(initialState, headers);
}
// Reuse client on the client-side
if (!globalApolloClient) {
globalApolloClient = createApolloClient(initialState, headers);
}
return globalApolloClient;
};
/**
* Creates a withApollo HOC
* that provides the apolloContext
* to a next.js Page or AppTree.
* @param {Object} withApolloOptions
* @param {Boolean} [withApolloOptions.ssr=false]
* @returns {(PageComponent: ReactNode) => ReactNode}
*/
export const withApollo = ({ ssr = true } = {}) => (PageComponent): any => {
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => {
let client;
if (apolloClient) {
// Happens on: getDataFromTree & next.js ssr
client = apolloClient;
} else {
// Happens on: next.js csr
// client = initApolloClient(apolloState, undefined);
client = initApolloClient(apolloState, {});
}
return (
<ApolloProvider client={client}>
<PageComponent {...pageProps} />
</ApolloProvider>
);
};
// Set the correct displayName in development
if (process.env.NODE_ENV !== 'production') {
const displayName =
PageComponent.displayName || PageComponent.name || 'Component';
WithApollo.displayName = `withApollo(${displayName})`;
}
if (ssr || PageComponent.getInitialProps) {
WithApollo.getInitialProps = async (ctx) => {
const { AppTree } = ctx
// Initialize ApolloClient, add it to the ctx object so
// we can use it in `PageComponent.getInitialProp`.
const apolloClient = (ctx.apolloClient = initApolloClient(null, await getHeaders(ctx)))
// Run wrapped getInitialProps methods
let pageProps = {}
if (PageComponent.getInitialProps) {
pageProps = await PageComponent.getInitialProps(ctx)
}
// Only on the server:
if (typeof window === 'undefined') {
// When redirecting, the response is finished.
// No point in continuing to render
if (ctx.res && ctx.res.finished) {
return pageProps
}
// Only if ssr is enabled
if (ssr) {
try {
// Run all GraphQL queries
const { getDataFromTree } = await import('@apollo/react-ssr')
await getDataFromTree(
<AppTree
pageProps={{
...pageProps,
apolloClient
}}
/>
)
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error)
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
}
}
// Extract query data from the Apollo store
const apolloState = apolloClient.cache.extract()
return {
...pageProps,
apolloState
}
}
}
return WithApollo;
};
The apolloClient.ts
import fetch from 'isomorphic-unfetch'
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { HttpLink } from 'apollo-link-http'
import { ApolloLink, Observable } from 'apollo-link';
import { onError } from 'apollo-link-error'
import { useMutation } from '@apollo/react-hooks';
// import { WebSocketLink } from 'apollo-link-ws'
// import { SubscriptionClient } from 'subscriptions-transport-ws'
let accessToken = null
const requestAccessToken = async () => {
if (accessToken) return
const res = await fetch(`${process.env.APP_HOST}/api/session`)
if (res.ok) {
const json = await res.json()
accessToken = json.accessToken
} else {
accessToken = 'public'
}
}
// remove cached token on 401 from the server
// const resetTokenLink = onError(({ networkError }) => {
// if (networkError && networkError.name === 'ServerError' && networkError.statusCode === 401) {
// accessToken = null
// }
// })
const request = async (operation: any) => {
// await renewalToken();
// const token = localStorage.getItem('user_token');
operation.setContext({
headers: {
// 'custom-header': 'custom'
// authorization: `Bearer ${token}`,
// 'x-tag': xTagGenerator()
}
});
};
const requestLink = new ApolloLink((operation: any, forward: any) =>
new Observable((observer: any) => {
let handle: any;
Promise.resolve(operation)
.then(oper => request(oper))
.then(() => {
handle = forward(operation).subscribe({
next: observer.next.bind(observer),
error: observer.error.bind(observer),
complete: observer.complete.bind(observer),
});
})
.catch(observer.error.bind(observer));
return () => {
if (handle) handle.unsubscribe();
};
})
);
const createHttpLink = (headers) => {
const httpLink = new HttpLink({
uri: `${process.env.API_URL}/graphql`,
credentials: 'include',
headers, // auth token is fetched on the server side
fetch,
})
return httpLink;
}
// const createWSLink = () => {
// return new WebSocketLink(
// new SubscriptionClient('wss://ready-panda-91.hasura.app/v1/graphql', {
// lazy: true,
// reconnect: true,
// connectionParams: async () => {
// // happens on the client
// await requestAccessToken()
// return {
// headers: {
// authorization: accessToken ? `Bearer ${accessToken}` : '',
// },
// }
// },
// })
// )
// }
export default function createApolloClient(initialState, headers) {
const ssrMode = typeof window === 'undefined'
const httpLink = createHttpLink(headers)
// let link
// if (ssrMode) {
// link = createHttpLink(headers) // executed on server
// } else {
// link = createWSLink() // executed on client
// }
return new ApolloClient({
ssrMode,
link: ApolloLink.from([
requestLink,
httpLink,
]),
cache: new InMemoryCache().restore(initialState),
})
}
From @codingcn 's code, there is an issue when you use full SSR, that used getInitialProps
in _app.ts
. This would happen when user gets the TOKEN at first time and be navigated to another page.
Here is the patch for the code:
const authLink = setContext((req, {headers}) => {
const clientSideToken = getCookie('accessToken',ctx.req); // Use your regular method to get token from client side
if(!accessToken) accessToken = clientSideToken;
return {
headers: {
...headers,
authorization: accessToken ? `Bearer ${accessToken}` : "",
}
}
});
Please add cookies from your route and get cookie from your init route and pass it into props. I hope it will work. Or show your auth.js file code.
I'm pretty much using the same setup used here https://github.com/vercel/next.js/tree/canary/examples/with-apollo except I get an auth token and set it up in the headers when creating the ApolloClient as shown below. I'm running into the same issue others have expressed where the authCookie below is coming back as null
until I refresh the page. Has anyone figured out how to solve this? Right now the workaround I have is that if I get an auth error I reload the page...however this is not ideal as I can end up in endless reloads.
For auth and how the cookie is set basically what happens is I'm using Auth0 and Auth0 redirects to a signed-in.js page that sets the cookie and then redirects using Next.js Router to the index.js page.
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import Cookie from 'js-cookie';
import { endpoint } from '../config';
// ....
function createApolloClient() {
const isBrowser = typeof window !== 'undefined';
const authCookie = (isBrowser) ? Cookie.get('idToken') : null;
return new ApolloClient({
connectToDevTools: isBrowser,
ssrMode: !isBrowser,
link: new HttpLink({
uri: endpoint, // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
headers: {
authorization: authCookie
}
}),
cache: new InMemoryCache(),
});
}
Hi all, I'm doing a bit of housekeeping. Since the most recent activity on this issue is more than a year old and there's no clear action to be taken by the maintainers, I'll close this for now. Happy to re-open if anyone feels strongly that there's a code or documentation update that should be made. Also feel free to post to our community forum. Thanks so much!
This should not be closed because the solutions provided are not adequate.
Hi @wwwhatley 👋🏻 can you share more details about your use case and expected vs. actual behavior?
We're closing this issue now but feel free to ping the maintainers or open a new issue if you still need support. Thank you!
please help, I tried all the examples and also other tutorials on the internet but no luck to fix the problem. I am using apollo client to get the data and I am using a cookie or Bearer (which one works) for authentication but in the first request and after every refresh, it is sending request with no cookie or Bearer in it, and my API rejects the request but in the second request which it happen immediately (automatically by app NOT user) after the first one it includes cookie and Bearer on it.
I think:
I tried to force the component to render just in client-side but no luck on it here is init-apollo.tsx file:
here is my withApollo.tsx code:
and _app.tsx file:
My Component: