SSR - Send request with cookie from apollo client with Next.js
  • 28-May-2023
Lightrun Team
Author Lightrun Team
Share
SSR - Send request with cookie from apollo client with Next.js

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

Lightrun Team
Lightrun Team
28-May-2023

Explanation of the problem

 

The application is facing an issue where the first request made using Apollo Client does not include the required authentication cookie or Bearer token, leading to the rejection of the request by the API. Despite trying various examples and tutorials from the internet, the problem persists. It appears that the first request is initiated from the server, which lacks access to the cookie, while the second request, triggered automatically after the first one, includes the necessary authentication details and succeeds. Attempts to render the component exclusively on the client-side have not resolved the issue.

To provide insights into the code involved, the init-apollo.tsx file handles the configuration of Apollo Client. It utilizes an HTTP link and an authentication link to establish the necessary connections. The authentication link is responsible for setting the authorization header with the Bearer token retrieved from the auth module. Here’s an excerpt of the code:

 

// init-apollo.tsx
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
  });
}

 

These code snippets demonstrate the configuration of the Apollo Client and the inclusion of the necessary authorization header with the Bearer token.

 

Troubleshooting with the Lightrun Developer Observability Platform

 

Getting a sense of what’s actually happening inside a live application is a frustrating experience, one that relies mostly on querying and observing whatever logs were written during development.
Lightrun is a Developer Observability Platform, allowing developers to add telemetry to live applications in real-time, on-demand, and right from the IDE.

  • Instantly add logs to, set metrics in, and take snapshots of live applications
  • Insights delivered straight to your IDE or CLI
  • Works where you do: dev, QA, staging, CI/CD, and production

Start for free today

Problem solution for: SSR – Send request with cookie from apollo client with Next.js

 

To address the issue where the first request made using Apollo Client is missing the authentication cookie or Bearer token, you can modify the code in withApollo.tsx. The getInitialProps method retrieves the token from the cookies and passes it to the initApollo function. However, the getInitialProps method is executed on the server-side during the initial page load, which may not have access to the cookies. You can modify the code to retrieve the token from the browser’s cookies when running on the client-side. Here’s an updated version of the 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 {
    apolloClient = undefined;
    static displayName = "withApollo(App)";

    constructor(props) {
      super(props);
      this.apolloClient = initApollo(props.apolloState, {
        getToken: () => {
          return parseCookies(undefined).token;
        },
      });
    }

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

      const apollo = initApollo(
        {},
        {
          getToken: () => {
            if (typeof window === "undefined") {
              return parseCookies(req).token;
            } else {
              return parseCookies(undefined).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 needs to be cleared manually
        Head.rewind();
      }

      // Extract query data from the Apollo store
      const apolloState = apollo.cache.extract();

      return {
        ...appProps,
        apolloState,
      };
    }

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

 

By checking whether the code is running on the server or the client-side, the updated code retrieves the token accordingly, ensuring it is available for the initial request.

Remember to update the withApollo HOC usage in _app.tsx to use the modified version:

 

import withApollo from "../helpers/with-apollo";

class MyApp extends App {
  // ...

  // Remove the existing withApollo export and wrap the component with the modified version
}

export default withApollo(MyApp);

 

With these modifications, the Apollo Client should include the authentication cookie or Bearer

 

Problems with apollo-client

 

Problem 1: CORS (Cross-Origin Resource Sharing) Issue Description: One common problem encountered with Apollo Client is the CORS issue. When making requests to a different origin, such as a different domain or port, the browser’s security mechanisms enforce CORS restrictions. This can lead to requests being blocked by the browser, resulting in failed API calls and errors.

Solution: To resolve the CORS issue, you need to configure the server to allow cross-origin requests. This typically involves setting the appropriate CORS headers on the server-side. Here’s an example using Express.js:

 

const express = require('express');
const cors = require('cors');

const app = express();

// Enable CORS for all routes
app.use(cors());

// Add your Apollo Server middleware and other routes

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

 

By using the cors middleware, you can allow cross-origin requests from any origin. You can also specify specific origins, methods, headers, and other configurations as needed.

Problem 2: Cache Invalidation Description: Another common problem with Apollo Client is cache invalidation. Apollo Client caches the results of GraphQL queries by default to improve performance and minimize unnecessary network requests. However, when data changes on the server-side, the client’s cache may become stale, resulting in outdated or incorrect data being displayed.

Solution: To address cache invalidation, Apollo Client provides mechanisms to manually update or invalidate cache entries. For example, you can use the refetchQueries option in a mutation to refetch specific queries after the mutation is completed:

 

import { useMutation } from '@apollo/client';

const DELETE_POST_MUTATION = gql`
  mutation DeletePost($postId: ID!) {
    deletePost(postId: $postId) {
      id
    }
  }
`;

const PostDeleteButton = ({ postId }) => {
  const [deletePost] = useMutation(DELETE_POST_MUTATION, {
    variables: { postId },
    refetchQueries: ['GetPosts'], // Refetch the 'GetPosts' query after deleting a post
  });

  return <button onClick={deletePost}>Delete</button>;
};

 

By specifying the refetchQueries option with the relevant query names, you can ensure that the affected queries are refetched, updating the cache with fresh data.

Problem 3: Subscription Handling Description: When working with real-time data using GraphQL subscriptions in Apollo Client, handling the subscription lifecycle and managing subscriptions across components can be a challenge. Subscriptions may need to be started, stopped, and updated based on user interactions or component lifecycle events.

Solution: To handle subscriptions effectively, Apollo Client provides hooks such as useSubscription that simplify subscription management. You can start and stop subscriptions based on component mount and unmount events:

 

import { useSubscription } from '@apollo/client';

const MESSAGE_SUBSCRIPTION = gql`
  subscription OnMessageAdded {
    messageAdded {
      id
      content
      createdAt
    }
  }
`;

const MessageList = () => {
  const { data, loading, error } = useSubscription(MESSAGE_SUBSCRIPTION);

  if (loading) {
    return <p>Loading messages...</p>;
  }

  if (error) {
    return <p>Error loading messages: {error.message}</p>;
  }

  const { messageAdded } = data;

  return (
    <ul>
      {messageAdded.map((message) => (
        <li key={message.id}>{message.content}</li>
      ))}
    </ul>
  );
};

 

With the useSubscription hook, you can easily subscribe to a GraphQL subscription and handle loading, error, and data updates within your components.

By leveraging the available tools and techniques provided by Apollo Client, you can overcome these common problems and build robust and reliable GraphQL-powered applications.

 

A brief introduction to apollo-client

 

Apollo Client is a powerful JavaScript library that provides a comprehensive set of tools for interacting with GraphQL APIs. It serves as a state management solution for managing GraphQL data in client-side applications. With Apollo Client, developers can efficiently fetch, cache, and manipulate data, as well as handle real-time updates through GraphQL subscriptions.

At its core, Apollo Client consists of a flexible and intuitive API that allows developers to perform GraphQL queries, mutations, and subscriptions. It seamlessly integrates with popular JavaScript frameworks such as React, Angular, and Vue.js, making it a versatile choice for building modern web applications. Apollo Client also provides advanced caching mechanisms, allowing for efficient data retrieval and synchronization across components. The cache can be easily normalized and updated, ensuring consistent and up-to-date data throughout the application.

Additionally, Apollo Client offers features like optimistic UI, which enables optimistic updates to the UI before the server responds, enhancing the perceived performance of the application. It also provides tools for handling errors and network connectivity, enabling graceful error handling and offline support. With its extensive ecosystem and active community support, Apollo Client continues to evolve and improve, making it a popular choice for working with GraphQL in client-side applications.

 

Most popular use cases for apollo-client

  1. Data Fetching and Management: Apollo Client is primarily used for fetching and managing data from GraphQL APIs. It provides an intuitive API for executing queries and mutations, as well as handling subscriptions. Here’s an example of how Apollo Client can be used to fetch data:

 

import { ApolloClient, gql } from '@apollo/client';

const client = new ApolloClient({
  // configuration options
});

const GET_USERS = gql`
  query getUsers {
    users {
      id
      name
      email
    }
  }
`;

client.query({ query: GET_USERS })
  .then(response => {
    console.log(response.data.users);
  })
  .catch(error => {
    console.error(error);
  });

 

  1. Caching and State Management: Apollo Client includes a powerful caching system that automatically stores and manages the data fetched from GraphQL APIs. This caching mechanism improves the performance of the application by reducing redundant network requests and providing instant access to previously fetched data. Developers can access and manipulate cached data using Apollo Client’s cache API. Here’s an example of accessing cached data:

 

import { ApolloClient, gql, InMemoryCache } from '@apollo/client';

const client = new ApolloClient({
  cache: new InMemoryCache(),
  // other configuration options
});

const GET_USER = gql`
  query getUser($id: ID!) {
    user(id: $id) {
      id
      name
      email
    }
  }
`;

// Assuming the user with ID '123' has been previously fetched and cached
const cachedUser = client.readQuery({
  query: GET_USER,
  variables: { id: '123' },
});

console.log(cachedUser);

 

  1. Real-time Updates and Subscriptions: Apollo Client provides built-in support for GraphQL subscriptions, enabling real-time updates in applications. Subscriptions allow clients to subscribe to specific data changes on the server and receive updates whenever the subscribed data changes. This feature is particularly useful for building collaborative applications, chat systems, real-time dashboards, and more. Here’s an example of subscribing to real-time updates using Apollo Client:

 

import { ApolloClient, gql } from '@apollo/client';

const client = new ApolloClient({
  // configuration options
});

const SUBSCRIBE_TO_MESSAGES = gql`
  subscription subscribeToMessages {
    messages {
      id
      text
      timestamp
    }
  }
`;

const subscription = client.subscribe({ query: SUBSCRIBE_TO_MESSAGES });

subscription.subscribe({
  next: response => {
    console.log(response.data.messages);
  },
  error: error => {
    console.error(error);
  },
});
Share

It’s Really not that Complicated.

You can actually understand what’s going on inside your live applications.

Try Lightrun’s Playground

Lets Talk!

Looking for more information about Lightrun and debugging?
We’d love to hear from you!
Drop us a line and we’ll get back to you shortly.

By clicking Submit I agree to Lightrun’s Terms of Use.
Processing will be done in accordance to Lightrun’s Privacy Policy.