question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

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

See original GitHub issue

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;

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:6
  • Comments:35 (2 by maintainers)

github_iconTop GitHub Comments

26reactions
mzygmuntcommented, Oct 10, 2019

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)
    })
}
9reactions
derozan10commented, 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),
  });
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Next.js With Apollo, SSR, Cookies, and Typescript
In this blog post I'll show you how I created a working Next.js TypeScript setup with Apollo Client. You can fetch data from...
Read more >
Server-side rendering - Apollo GraphQL Docs
Server-side rendering (SSR) is a performance optimization for modern web apps. It enables you to render your app's initial state to raw HTML...
Read more >
Next.js: React Apollo Client Not sending cookies?
Surprisingly, when I make a mutation to the graphql server I'm able to set the cookies but, I'm not able to get the...
Read more >
Client and Server Side Cookies in Next.js - YouTube
Let's learn how to set/remove cookies both in the browser but also on the server in Next. js. This will allow us to...
Read more >
Using cookies to authenticate Next.js + Apollo GraphQL ...
Using cookies to authenticate Next.js + Apollo GraphQL requests ; // pages/api/graphql/index.ts // https://github.com/zeit/next.js/blob/master/ ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found