Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SSR - Send request with cookie from apollo client with Next.js #5089

Closed
saostad opened this issue Jul 24, 2019 · 36 comments
Closed

SSR - Send request with cookie from apollo client with Next.js #5089

saostad opened this issue Jul 24, 2019 · 36 comments

Comments

@saostad
Copy link

saostad commented Jul 24, 2019

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:

  • the first request is coming from the server (which doesn't have access to cookie)
  • the second request is from the client (has access to cookie)

I tried to force the component to render just in client-side but no luck on it
here is init-apollo.tsx file:

import { ApolloClient, InMemoryCache } from "apollo-boost";
import fetch from "isomorphic-unfetch";
import { setContext } from "apollo-link-context";
import { createHttpLink } from "apollo-link-http";
import { variables } from "./variables";
import { auth } from "./auth";
import { isBrowser } from "./utils";

let apolloClient = null;

function create(initialState, { fetchOptions }) {
  const httpLink = createHttpLink({
    uri: variables.apiUri,
    credentials: "include",
    fetch: isBrowser ? fetch : undefined,
    fetchOptions
  });

  const authLink = setContext((_, { headers }) => {
    const token = auth.getUserToken();
    return {
      headers: {
        ...headers,
        authorization: token ? `Bearer ${token}` : ""
      }
    };
  });

  return new ApolloClient({
    connectToDevTools: isBrowser,
    ssrMode: false,
    link: authLink.concat(httpLink),
    cache: new InMemoryCache().restore(initialState || {}),
    ssrForceFetchDelay: 100
  });
}

here is my withApollo.tsx code:

import React from "react";
import cookie from "cookie";
import initApollo from "./init-apollo";
import Head from "next/head";
import { getDataFromTree } from "react-apollo";

function parseCookies(req, options = {}) {
  return cookie.parse(
    req ? req.headers.cookie || "" : document.cookie,
    options
  );
}

export default App => {
  return class WithApollo extends React.Component {
    public constructor(props) {
      super(props);
      this.apolloClient = initApollo(props.apolloState, {
        getToken: () => {
          return parseCookies(undefined).token;
        }
      });
    }
    public apolloClient = undefined;
    public static displayName = "withApollo(App)";

    public static async getInitialProps(ctx) {
      const {
        Component,
        router,
        ctx: { req, res }
      } = ctx;

      const apollo = initApollo(
        {},
        {
          getToken: () => parseCookies(req).token
        }
      );

      ctx.ctx.apolloClient = apollo;

      let appProps = {};
      if (App.getInitialProps) {
        appProps = await App.getInitialProps(ctx);
      }

      if (res && res.finished) {
        // When redirecting, the response is finished.
        // No point in continuing to render
        return {};
      }

      if (typeof window === "undefined") {
        try {
          // Run all GraphQL queries
          await getDataFromTree(
            <App
              {...appProps}
              Component={Component}
              router={router}
              apolloClient={apollo}
            />
          );
        } 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 = apollo.cache.extract();

      return {
        ...appProps,
        apolloState
      };
    }

    public render() {
      return <App {...this.props} apolloClient={this.apolloClient} />;
    }
  };
};

and _app.tsx file:

import React from "react";
import App, { Container } from "next/app";

import { runWithAdal } from "react-adal";
import { authContext } from "../helpers/adal-config";
import AppLayout from "../components/common/AppLayout";
import GlobalContext from "../helpers/global-context";
import { auth } from "../helpers/auth";
import withApollo from "../helpers/with-apollo";
import { ApolloProvider } from "react-apollo";

class MyApp extends App {
  public state = {
    isLoggedIn: auth.getUserProfile() ? true : false,
    userProfile: auth.getUserProfile(),
    logout: () => {
      auth.logOut();
    }
  };

  public static async getInitialProps({ Component, ctx }) {
    let pageProps = {};

    if (Component.getInitialProps) {
      pageProps = await Component.getInitialProps(ctx);
    }

    return { pageProps };
  }
  public render() {
    // @ts-ignore
    const { Component, pageProps, apolloClient } = this.props;

    return (
      <Container>
        <ApolloProvider client={apolloClient}>
          <GlobalContext.Provider value={this.state}>
            <AppLayout>
              <Component {...pageProps} />
            </AppLayout>
          </GlobalContext.Provider>
        </ApolloProvider>
      </Container>
    );
  }
}

/** @false: Login when app loads
 * @true: do not login when app loads (wait for other components to ask for login)
 */
const doNotLogin = false;
runWithAdal(
  authContext,
  () => {
    return MyApp;
  },
  doNotLogin
);

export default withApollo(MyApp);

My Component:

import { Query } from "react-apollo";
import gql from "graphql-tag";
import Loading from "./common/Loading";
import Error from "./common/Error";
import { isBrowser } from "../helpers/utils";

const helloQuery = gql`
  query hello {
    hello
  }
`;

const PostList = function PostList() {
  return (
    <Query query={helloQuery} ssr={false}>
      {({ loading, error, data }) => {
        if (error) {
          return <Error />;
        }
        if (loading) {
          return <Loading />;
        }

        return <section>{data.hello as string}</section>;
      }}
    </Query>
  );
};
export default PostList;

@saostad saostad changed the title Send request with cookie from apollo client SSR - Send request with cookie from apollo client with Next.js Jul 24, 2019
@mzygmunt
Copy link

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)
    })
}

@juicycleff
Copy link

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

@saostad
Copy link
Author

saostad commented Nov 4, 2019

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.

@sanojsilva
Copy link

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.

@saostad
Copy link
Author

saostad commented Nov 11, 2019

I added Cors in my server configuration

@chemicalkosek
Copy link

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.

@saostad
Copy link
Author

saostad commented Jan 10, 2020

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?

@chemicalkosek
Copy link

chemicalkosek commented Jan 10, 2020

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

@MattXtrem
Copy link

MattXtrem commented Jan 14, 2020

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, }

@chemicalkosek
Copy link

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

@MattXtrem
Copy link

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

@nadzic
Copy link

nadzic commented Jan 15, 2020

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?

@chemicalkosek
Copy link

chemicalkosek commented Jan 21, 2020

@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

@derozan10
Copy link

derozan10 commented Mar 13, 2020

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),
  });
}

@mjurincic
Copy link

@derozan10 Can you point which nextjs example are you referring to.

@derozan10
Copy link

@derozan10 Can you point which nextjs example are you referring to.

this one

@mjurincic
Copy link

@derozan10 Can you point which nextjs example are you referring to.

this one

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>
      );
    },
  },
);

@chemicalkosek
Copy link

chemicalkosek commented Mar 31, 2020

@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 });

@mjurincic
Copy link

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.

@sushilbansal
Copy link

Hi @mjurincic: did you figure out the issue?

@EliasTouil
Copy link

EliasTouil commented May 10, 2020

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
#5089 (comment)

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"
  }

@ODelibalta
Copy link

ODelibalta commented Jul 4, 2020

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.

login.js section

  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);

Screen Shot 2020-07-04 at 12 57 56 PM

@dmitry
Copy link

dmitry commented Jul 4, 2020

@ODelibalta what does the ctx shows?

@ODelibalta
Copy link

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),
  });
}

@lionelyoung
Copy link

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,
    },
  }
}

@codingcn
Copy link

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

@shoaibsharif
Copy link

shoaibsharif commented Aug 2, 2020

@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.

@platypusrex
Copy link

@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.

JonathanFeller added a commit to JonathanFeller/reddit-clone-web that referenced this issue Nov 25, 2020
@phattranky
Copy link

Those changes are working at my side to send the cookie from NextJS Server Side Rendering to the Server (I'm using NestJS)

111

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),
  })
}

@Tokenyet
Copy link

Tokenyet commented Apr 16, 2021

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}` : "",
            }
        }
    });

@zobayerhossainmamun
Copy link

zobayerhossainmamun commented Jul 12, 2021

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.

@SysSU
Copy link

SysSU commented Sep 29, 2021

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(),
  });
}

@bignimbus
Copy link
Contributor

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!

@bignimbus bignimbus reopened this Nov 3, 2022
@bignimbus bignimbus closed this as not planned Won't fix, can't repro, duplicate, stale Nov 3, 2022
@wwwhatley
Copy link

This should not be closed because the solutions provided are not adequate.

@bignimbus
Copy link
Contributor

Hi @wwwhatley 👋🏻 can you share more details about your use case and expected vs. actual behavior?

@github-actions
Copy link
Contributor

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!

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Jan 20, 2023
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 19, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests