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

Invariant Violation: An error occurred! For more details, see the full error text at https://go.apollo.dev/c/err#%7B%22version%22%3A%223.9.4%22%2C%22message%22%3A49%2C%22args%22%3A%5B%5D%7D #11596

Closed
MarvinTchio opened this issue Feb 14, 2024 · 8 comments

Comments

@MarvinTchio
Copy link

MarvinTchio commented Feb 14, 2024

I have the following set up in my Next.js x Typescript x React app and I cant figure out why I'm getting this error or how to get past it. Help please...

import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from 'next';
import React from 'react';
import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import { gql } from '@apollo/client';
import client from '../libs/apolloClient';
import { BlogPost as Post } from '../libs/blogTypes';
import { FacebookShareButton, TwitterShareButton } from 'next-share';
import { FaFacebookF, FaTwitter } from "react-icons/fa";


const GET_ALL_POSTS_SLUGS = gql`
  query GetAllPostsSlugs {
    pageBlogPostCollection {
      items {
        slug
      }
    }
  }
`;

const GET_POST_BY_SLUG = gql`
  query GetPostBySlug($slug: String!) {
    pageBlogPostCollection(where: { slug: $slug }, limit: 1) {
      items {
        sys {
          id
        }
        title
        slug
        shortDescription
        featuredImage {
          url
          title
        }
        content {
          json
        }
      }
    }
  }
`;

export const getStaticPaths: GetStaticPaths = async () => {
  const { data } = await client.query({ query: GET_ALL_POSTS_SLUGS });
  const paths = data.pageBlogPostCollection.items.map((item: { slug: string }) => ({
    params: { slug: item.slug },
  }));

  return { paths, fallback: 'blocking' };
};

export const getStaticProps: GetStaticProps<{ post?: Post }> = async ({ params }) => {
  if (!params || typeof params.slug !== 'string') {
    return { notFound: true };
  }
  const { slug } = params;

  const { data } = await client.query({
    query: GET_POST_BY_SLUG,
    variables: { slug },
  });

  const post = data.pageBlogPostCollection.items.length > 0 ? data.pageBlogPostCollection.items[0] : null;

  return post ? { props: { post } } : { notFound: true };
};

const PostPage: React.FC<InferGetStaticPropsType<typeof getStaticProps>> = ({ post }) => {
  if (!post) {
    return <div>Post not found</div>;
  }

  const postUrl = `https://yourwebsite.com/blog/${post.slug}`;

  return (
    <article>
      <h1>{post.title}</h1>
      {post.shortDescription && <h2>{post.shortDescription}</h2>}
      {post.featuredImage && (
        <img
          src={post.featuredImage.url}
          alt={post.featuredImage.title || 'Featured image'}
        />
      )}
      <div>{documentToReactComponents(post.content.json)}</div>
    </article>
  );
};

export default PostPage;
'use client';
import React from 'react';
import { useQuery } from '@apollo/client';
import Link from 'next/link';
import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import { GET_ALL_POSTS_QUERY } from '../libs/contentful'; // Import your GraphQL query
import { BlogPost as Post } from '../libs/blogTypes'; // Import your TypeScript types

const BlogClient = () => {
  const { data, loading, error } = useQuery<{ pageBlogPostCollection: { items: Post[] } }>(GET_ALL_POSTS_QUERY);

  if (loading) return <div className="blog-loading">Loading...</div>;
  if (error) return <div className="blog-error">Error: {error.message}</div>;

  return (
    <div className="blog-container">
      {data?.pageBlogPostCollection.items.map((post) => (
        <article key={post.sys.id} className="blog-post">
          <h2 className="post-title">
            <Link href={`/blog/${post.slug}`}>
              <a>{post.title}</a>
            </Link>
          </h2>
          {post.shortDescription && (
            <p className="post-subtitle">{post.shortDescription}</p>
          )}
          {post.featuredImage && (
            <img
              className="post-image"
              src={post.featuredImage.url}
              alt={post.featuredImage.title || 'Featured image'}
            />
          )}
          <div className="post-excerpt">
            {documentToReactComponents(post.content.json)}
          </div>
        </article>
      ))}
    </div>
  );
};

export default BlogClient;

import React from 'react';
import BlogClient from './BlogClient'; 
import ClientOnly from '@/app/components/ClientOnly'; 

const BlogPage = () => {
  return (
    <ClientOnly>
      <BlogClient />
    </ClientOnly>
  );
};

export default BlogPage;

import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client';
import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev";

if (process.env.NODE_ENV !== "production") {
  loadDevMessages();
  loadErrorMessages();
}

const space = process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID;
const accessToken = process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN;
const endpoint = `https://graphql.contentful.com/content/v1/spaces/${space}/environments/master`;

const httpLink = new HttpLink({
  uri: endpoint,
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

 const client = new ApolloClient({
  link: httpLink,
  cache: new InMemoryCache(),
});

export default client;

import { ApolloClient, InMemoryCache, HttpLink, gql } from '@apollo/client';
import { BlogPost as Post } from './blogTypes';

const space = process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID;
const accessToken = process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN;
const client = new ApolloClient({
  link: new HttpLink({
    uri: `https://graphql.contentful.com/content/v1/spaces/${space}`,
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  }),
  cache: new InMemoryCache(),
});

// GraphQL queries
export const GET_ALL_POSTS_QUERY = gql`
  query GetAllPosts {
    pageBlogPostCollection {
      items {
        sys {
          id
        }
        title
        slug
        shortDescription
        featuredImage {
          url
          title
          description
        }
        content {
          json
        }
      }
    }
  }
`;

export const GET_POST_BY_SLUG_QUERY = gql`
  query GetPostBySlug($slug: String!) {
    pageBlogPostCollection(where: { slug: $slug }, limit: 1) {
      items {
        sys {
          id
        }
        title
        slug
        shortDescription
        featuredImage {
          url
          title
          description
        }
        content {
          json
        }
      }
    }
  }
`;

// Fetch all blog posts
export async function fetchEntries(): Promise<Post[]> {
  const { data } = await client.query<{ pageBlogPostCollection: { items: Post[] } }>({ query: GET_ALL_POSTS_QUERY });
  return data.pageBlogPostCollection.items;
}

// Fetch a single post by slug
export async function fetchSingleEntry(slug: string): Promise<Post | null> {
  const { data } = await client.query<{ pageBlogPostCollection: { items: Post[] } }>({
    query: GET_POST_BY_SLUG_QUERY,
    variables: { slug },
  });
  return data.pageBlogPostCollection.items.length ? data.pageBlogPostCollection.items[0] : null;
}
import { gql } from '@apollo/client';
import client from './apolloClient'; 


const GET_ENTRIES_QUERY = gql`
  query GetEntries($contentType: String!) {
    entryCollection(where: { contentType: $contentType }) {
      items {
        sys {
          id
        }
        ... on BlogPost { // Replace BlogPost with your actual Content Type ID
          title
          slug
          content
        }
      }
    }
  }
`;

const GET_SINGLE_ENTRY_QUERY = gql`
  query GetSingleEntry($entryId: String!) {
    blogPost(id: $entryId) {
      sys {
        id
      }
      title
      slug
      content
    }
  }
`;

// Fetch multiple entries by content type
export async function fetchEntries(contentType: string) {
  const { data } = await client.query({
    query: GET_ENTRIES_QUERY,
    variables: { contentType },
  });

  return data.entryCollection.items || [];
}

// Fetch a single entry by entry ID
export async function fetchSingleEntry(entryId: string) {
  const { data } = await client.query({
    query: GET_SINGLE_ENTRY_QUERY,
    variables: { entryId },
  });


  return data.blogPost || null;
}


@phryneas
Copy link
Member

It seems like you do not have an <ApolloProvider in your application.

Generally, please note that you should not be using @apollo/client on it's own in the app directory.

We have a separate package to integrate Apollo Client with the Next.js app router:

https://www.apollographql.com/blog/using-apollo-client-with-next-js-13-releasing-an-official-library-to-support-the-app-router

@jerelmiller jerelmiller added the 🏓 awaiting-contributor-response requires input from a contributor label Feb 14, 2024
@MarvinTchio
Copy link
Author

MarvinTchio commented Feb 14, 2024

Now I have the following set up and the error I'm facing is : - Screenshot 2024-02-14 at 4 37 58 PM

- error node_modules/@apollo/experimental-nextjs-app-support/dist/ssr/RehydrationContext.js (28:0) @ RehydrationContextProvider
- error Error: When using Next SSR, you must use the `NextSSRApolloClient`
import { GetStaticPaths, GetStaticProps, InferGetStaticPropsType } from 'next';
import React from 'react';
import { gql } from '@apollo/client';
import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import { getClient } from '../libs/apolloServer'; // Adjust the import path as necessary
import { BlogPost as Post } from '../libs/blogTypes';
import { FacebookShareButton, TwitterShareButton } from 'next-share';
import { FaFacebookF, FaTwitter } from "react-icons/fa";

// Type for the object that includes the slug
interface PostSlug {
  slug: string;
}

// GraphQL queries to fetch the slugs for all posts and the details of a post by its slug
const GET_ALL_POSTS_SLUGS = gql`
  query GetAllPostsSlugs {
    pageBlogPostCollection {
      items {
        slug
      }
    }
  }
`;

const GET_POST_BY_SLUG = gql`
  query GetPostBySlug($slug: String!) {
    pageBlogPostCollection(where: { slug: $slug }, limit: 1) {
      items {
        sys {
          id
        }
        title
        slug
        shortDescription
        featuredImage {
          url
          title
        }
        content {
          json
        }
      }
    }
  }
`;

export const getStaticPaths: GetStaticPaths = async () => {
  const apolloClient = getClient();
  const { data } = await apolloClient.query({ query: GET_ALL_POSTS_SLUGS });
  const paths = data.pageBlogPostCollection.items.map(({ slug }: PostSlug) => ({
    params: { slug },
  }));

  return { paths, fallback: 'blocking' };
};

export const getStaticProps: GetStaticProps<{ post?: Post }> = async ({ params }) => {
  if (!params?.slug || typeof params.slug !== 'string') {
    return { notFound: true };
  }
  const apolloClient = getClient();
  const { data } = await apolloClient.query({
    query: GET_POST_BY_SLUG,
    variables: { slug: params.slug },
  });

  const post = data.pageBlogPostCollection.items.length > 0 ? data.pageBlogPostCollection.items[0] : null;

  if (!post) {
    return { notFound: true };
  }

  return { props: { post }, revalidate: 1 };
};

const PostPage: React.FC<InferGetStaticPropsType<typeof getStaticProps>> = ({ post }) => {
  if (!post) {
    return <div>Post not found.</div>;
  }

  const postUrl = `https://yourdomain.com/blog/${post.slug}`;

  return (
    <article>
      <h1>{post.title}</h1>
      {post.shortDescription && <p>{post.shortDescription}</p>}
      {post.featuredImage && (
        <img
          src={post.featuredImage.url}
          alt={post.featuredImage.title || 'Featured image'}
          style={{ width: '100%' }}
        />
      )}
      <div>{documentToReactComponents(post.content.json)}</div>
      <div className="social-share-buttons">
        <FacebookShareButton url={postUrl}>
          <FaFacebookF size={32} />
        </FacebookShareButton>
        <TwitterShareButton url={postUrl}>
          <FaTwitter size={32} />
        </TwitterShareButton>
      </div>
    </article>
  );
};

export default PostPage;

'use client';

import React from 'react';
import { useQuery } from '@apollo/client';
import Link from 'next/link';
import { documentToReactComponents } from '@contentful/rich-text-react-renderer';
import { GET_ALL_POSTS_QUERY } from '../libs/contentful';
import { BlogPost as Post } from '../libs/blogTypes';

const BlogClient = () => {
  const { data, loading, error } = useQuery<{ pageBlogPostCollection: { items: Post[] } }>(GET_ALL_POSTS_QUERY);

  if (loading) return <div className="blog-loading">Loading...</div>;
  if (error) return <div className="blog-error">Error: {error.message}</div>;

  return (
    <div className="blog-container">
      {data?.pageBlogPostCollection.items.map((post) => (
        <article key={post.sys.id} className="blog-post">
          <h2 className="post-title">
            <Link href={`/blog/${post.slug}`}>
              {post.title}
            </Link>
          </h2>
          {post.shortDescription && (
            <p className="post-subtitle">{post.shortDescription}</p>
          )}
          {post.featuredImage && (
            <img
              className="post-image"
              src={post.featuredImage.url}
              alt={post.featuredImage.title || 'Featured image'}
            />
          )}
          <div className="post-excerpt">
            {documentToReactComponents(post.content.json)}
          </div>
        </article>
      ))}
    </div>
  );
};

export default BlogClient;


import React from 'react';
import BlogClient from './BlogClient'; 
import ClientOnly from '@/app/components/ClientOnly'; 

const BlogPage = () => {
  return (
    <ClientOnly>
      <BlogClient />
    </ClientOnly>
  );
};

export default BlogPage;




import React, { Suspense } from 'react';
import { gql } from '@apollo/client';
import { useSuspenseQuery } from "@apollo/experimental-nextjs-app-support/ssr";
import { ApolloWrapper } from '../libs/ApolloWrapper'; // Adjust the import path as necessary
import { BlogPost } from '../libs/blogTypes'; // Adjust the import path as necessary
import { documentToReactComponents } from '@contentful/rich-text-react-renderer';

const BLOG_POSTS_QUERY = gql`
  query GetBlogPosts {
    blogPostsCollection { // Update with actual query and fields
      items {
        sys {
          id
        }
        title
        slug
        shortDescription
        featuredImage {
          url
          title
        }
        content {
          json
        }
      }
    }
  }
`;

const PollPage = () => {
  const { data } = useSuspenseQuery<{ blogPostsCollection: { items: BlogPost[] } }>(BLOG_POSTS_QUERY);
  const blogPosts = data.blogPostsCollection.items;

  return (
    <div>
      {blogPosts.map((post) => (
        <article key={post.sys.id}>
          <h2>{post.title}</h2>
          {post.shortDescription && <p>{post.shortDescription}</p>}
          {post.featuredImage && (
            <img
              src={post.featuredImage.url}
              alt={post.featuredImage.title || 'Featured Image'}
            />
          )}
          <div>{documentToReactComponents(post.content.json)}</div>
        </article>
      ))}
    </div>
  );
};

const PollPageWithSuspense = () => (
  <ApolloWrapper>
    <Suspense fallback={<div>Loading...</div>}>
      <PollPage />
    </Suspense>
  </ApolloWrapper>
);

export default PollPageWithSuspense;

import { ApolloClient, HttpLink, InMemoryCache, ApolloLink } from '@apollo/client';
import { NextSSRInMemoryCache, SSRMultipartLink } from '@apollo/experimental-nextjs-app-support/ssr';

// Configuration for connecting to your GraphQL endpoint
const space = process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID;
const accessToken = process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN;
const endpoint = `https://graphql.contentful.com/content/v1/spaces/${space}/environments/master`;

// HTTP Link setup for fetching GraphQL data
const httpLink = new HttpLink({
  uri: endpoint,
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

// Client Components Apollo Client setup with SSR support
export function makeClient() {
  return new ApolloClient({
    cache: new NextSSRInMemoryCache(),
    link: typeof window === "undefined"
      ? ApolloLink.from([new SSRMultipartLink({ stripDefer: true }), httpLink])
      : httpLink,
  });
}

import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'; // Import ApolloClient here
import { registerApolloClient } from '@apollo/experimental-nextjs-app-support/rsc';

// Configuration for connecting to your GraphQL endpoint
const space = process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID;
const accessToken = process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN;
const endpoint = `https://graphql.contentful.com/content/v1/spaces/${space}/environments/master`;

// HTTP Link setup for fetching GraphQL data
const httpLink = new HttpLink({
  uri: endpoint,
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

// Server Components Apollo Client setup
export const { getClient } = registerApolloClient(() => new ApolloClient({ // ApolloClient is now correctly imported
  cache: new InMemoryCache(),
  link: httpLink,
}));

'use client';
import React from 'react';
import { ApolloNextAppProvider } from '@apollo/experimental-nextjs-app-support/ssr';
import { makeClient } from './apolloClient'; // Adjust the import path as necessary

export function ApolloWrapper({ children }: React.PropsWithChildren<{}>): JSX.Element {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
}



import { ApolloClient, InMemoryCache, HttpLink, gql } from '@apollo/client';
import { BlogPost as Post } from './blogTypes'; 

const space = process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID;
const accessToken = process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN;
const client = new ApolloClient({
  link: new HttpLink({
    uri: `https://graphql.contentful.com/content/v1/spaces/${space}`,
    headers: {
      Authorization: `Bearer ${accessToken}`,
    },
  }),
  cache: new InMemoryCache(),
});

// GraphQL queries
export const GET_ALL_POSTS_QUERY = gql`
  query GetAllPosts {
    pageBlogPostCollection {
      items {
        sys {
          id
        }
        title
        slug
        shortDescription
        featuredImage {
          url
          title
          description
        }
        content {
          json
        }
      }
    }
  }
`;

export const GET_POST_BY_SLUG_QUERY = gql`
  query GetPostBySlug($slug: String!) {
    pageBlogPostCollection(where: { slug: $slug }, limit: 1) {
      items {
        sys {
          id
        }
        title
        slug
        shortDescription
        featuredImage {
          url
          title
          description
        }
        content {
          json
        }
      }
    }
  }
`;

// Fetch all blog posts
export async function fetchEntries(): Promise<Post[]> {
  const { data } = await client.query<{ pageBlogPostCollection: { items: Post[] } }>({ query: GET_ALL_POSTS_QUERY });
  return data.pageBlogPostCollection.items;
}

// Fetch a single post by slug
export async function fetchSingleEntry(slug: string): Promise<Post | null> {
  const { data } = await client.query<{ pageBlogPostCollection: { items: Post[] } }>({
    query: GET_POST_BY_SLUG_QUERY,
    variables: { slug },
  });
  return data.pageBlogPostCollection.items.length ? data.pageBlogPostCollection.items[0] : null;
}

import { gql } from '@apollo/client';
import { getClient } from './apolloServer'; // Expects a server-side Apollo Client setup
import { makeClient } from './apolloClient'; // Expects a client-side Apollo Client setup

// Interfaces for typing the responses from Contentful's GraphQL API
interface Sys {
  id: string;
}

interface BlogPost {
  sys: Sys;
  title: string;
  slug: string;
  content: string; // Adjust according to your actual content model
}

interface EntryCollectionResponse {
  entryCollection: {
    items: BlogPost[];
  };
}

interface SingleEntryResponse {
  blogPost: BlogPost;
}

const GET_ENTRIES_QUERY = gql`
  query GetEntries($contentType: String!) {
    entryCollection(where: { contentType: $contentType }) {
      items {
        sys {
          id
        }
        ... on BlogPost {
          title
          slug
          content
        }
      }
    }
  }
`;

const GET_SINGLE_ENTRY_QUERY = gql`
  query GetSingleEntry($entryId: String!) {
    blogPost(id: $entryId) {
      sys {
        id
      }
      title
      slug
      content
    }
  }
`;

// Function to dynamically select the Apollo Client based on execution context
function useApolloClient() {
  // This function decides whether to use the server or client instance
  // of Apollo Client based on the environment.
  return typeof window === 'undefined' ? getClient : makeClient;
}

// Fetch multiple entries by content type
export async function fetchEntries(contentType: string): Promise<BlogPost[]> {
  const client = useApolloClient();
  const { data } = await client().query<EntryCollectionResponse>({
    query: GET_ENTRIES_QUERY,
    variables: { contentType },
  });

  return data.entryCollection.items;
}

// Fetch a single entry by entry ID
export async function fetchSingleEntry(entryId: string): Promise<BlogPost | null> {
  const client = useApolloClient();
  const { data } = await client().query<SingleEntryResponse>({
    query: GET_SINGLE_ENTRY_QUERY,
    variables: { entryId },
  });

  return data.blogPost;
}

I also imported the Apollo Wrapper into my layout and wrapped it around all children components as shown here:

return (
    <html lang="en">
      <body className={font.className}>
      <ApolloWrapper>

        <ClientOnly>
          <ToasterProvider />
          <LoginModal />
          <RegisterModal />
          <SearchModal />
          <RentModal />
          <ConfirmModal />
          <WelcomeModal />
          <DialogModal />
          <DeleteDialogModal />
          <ProfileProvider>
          <ProfileInfoModal />
          </ProfileProvider>
          <Navbar currentUser={currentUser} />
          <ProfileProvider>
          <ProfileModal userId={currentUser?.id} email={currentUser?.email} />
          </ProfileProvider>
        </ClientOnly>

        <div className="flex flex-col min-h-screen">
        <div className="flex-grow pb-20 pt-28">
        <GoogleTagManager id="GTM-PFV4NM5Z" />
          {children}
          <Analytics />
        </div>
        <Footer />
      </div>
      </ApolloWrapper>

      </body>
    </html>
  )
}

@MarvinTchio
Copy link
Author

Given the Client Components Setup with SSR Support using ApolloClient directly was causing the error because for SSR scenarios, NextSSRApolloClient should be used instead, as indicated by the error messages, I updated the following files and now it works:


'use client';
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import { makeClient } from './apolloClient';

// Explicitly type the props for the component, including typing children as React.ReactNode
interface ApolloWrapperProps {
  children: React.ReactNode;
}

const ApolloWrapper: React.FC<ApolloWrapperProps> = ({ children }) => {
  const client = makeClient();
  return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

export default ApolloWrapper;

'use client';
import React from 'react';
import { ApolloProvider } from '@apollo/client';
import { makeClient } from './apolloClient';

// Explicitly type the props for the component, including typing children as React.ReactNode
interface ApolloWrapperProps {
  children: React.ReactNode;
}

const ApolloWrapper: React.FC<ApolloWrapperProps> = ({ children }) => {
  const client = makeClient();
  return <ApolloProvider client={client}>{children}</ApolloProvider>;
};

export default ApolloWrapper;

Don't see the immediate benefits I was expecting from implementing GraphQL but its been quite something implementing this. I just hope it improves the fetching of my blog content as promised.

@github-actions github-actions bot removed the 🏓 awaiting-contributor-response requires input from a contributor label Feb 15, 2024
@phryneas
Copy link
Member

phryneas commented Feb 15, 2024

I'm very sorry you have to go through all this - unfortunately, at this time it's no fun using Next.js with a third-party fetching library, since Next.js runs your code

  • in a React Server Component build run
  • in a SSR run (use client)
  • in the browser (use client)

and all of those need slightly different setup.

Unfortunately, there is not much we can do about that added complexity :/

Given the Client Components Setup with SSR Support using ApolloClient directly was causing the error because for SSR scenarios, NextSSRApolloClient should be used instead, as indicated by the error messages,

I get that it looks like it is working for you now, but this will execute the same queries once during the SSR run and then again in the browser - is there a reason why you are not using the NextSSRApolloClient as asked by that error message?

I know that the setup is no fun since you have to set up for three environments, but there is really no way around it :/

@MarvinTchio
Copy link
Author

MarvinTchio commented Feb 15, 2024

I forgot to mention this, the main issue with respect to the NextSSRApolloClient issue was that it needed to be distinguished between the client and server side of the apolloClient when in the browser so I needed to ensure that when the ApolloClient got called from fetchContentful.ts it was checked first. The following code displays the separation of the apolloClient.ts into two components (apolloClient.ts and apolloServer.ts) that would run separately and Called when needed by Contentful.

import { HttpLink, InMemoryCache } from '@apollo/client';
import { ApolloLink } from '@apollo/client';
import { NextSSRApolloClient, SSRMultipartLink } from '@apollo/experimental-nextjs-app-support/ssr';
import { loadErrorMessages, loadDevMessages } from "@apollo/client/dev";

// Load development-specific error messages
if (process.env.NODE_ENV !== "production") {
  loadDevMessages();
  loadErrorMessages();
}

// Configuration for connecting to your GraphQL endpoint
const space = process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID;
const accessToken = process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN;
const endpoint = `https://graphql.contentful.com/content/v1/spaces/${space}/environments/master`;

// HTTP Link setup for fetching GraphQL data
const httpLink = new HttpLink({
  uri: endpoint,
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

// Function to create an Apollo Client instance
export function makeClient() {
  // Adjust to use NextSSRApolloClient for SSR with appropriate caching and link setup
  return new NextSSRApolloClient({
    link: typeof window === "undefined"
      ? ApolloLink.from([new SSRMultipartLink({ stripDefer: true }), httpLink])
      : httpLink,
    cache: new InMemoryCache(),
  });
}

import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';
import { registerApolloClient } from '@apollo/experimental-nextjs-app-support/rsc';

// Configuration for connecting to your GraphQL endpoint
const space = process.env.NEXT_PUBLIC_CONTENTFUL_SPACE_ID;
const accessToken = process.env.NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN;
const endpoint = `https://graphql.contentful.com/content/v1/spaces/${space}/environments/master`;

// HTTP Link setup for fetching GraphQL data
const httpLink = new HttpLink({
  uri: endpoint,
  headers: {
    Authorization: `Bearer ${accessToken}`,
  },
});

// Server Components Apollo Client setup
export const { getClient } = registerApolloClient(() => new ApolloClient({ 
  cache: new InMemoryCache(),
  link: httpLink,
}));

import { gql } from '@apollo/client';
import { getClient } from './apolloServer'; 
import { makeClient } from './apolloClient';

// Interfaces for typing the responses from Contentful's GraphQL API
interface Sys {
  id: string;
}

interface BlogPost {
  sys: Sys;
  title: string;
  slug: string;
  content: string; // Adjust according to your actual content model
}

interface EntryCollectionResponse {
  entryCollection: {
    items: BlogPost[];
  };
}

interface SingleEntryResponse {
  pageBlogPost: BlogPost;
}

const GET_ENTRIES_QUERY = gql`
query GetEntries {
  pageBlogPostCollection {
    items {
      sys {
        id
      }
      title
      slug
      content {
        json
      }
    }
  }
}
`;

const GET_SINGLE_ENTRY_QUERY = gql`
  query GetSingleEntry($entryId: String!) {
    pageBlogPost(id: $entryId) {
      sys {
        id
      }
      title
      slug
      content {
        json 
      }
    }
  }
`;

function getApolloClient() {
  // If window is undefined, we are on the server, otherwise we are on the client.
  return typeof window === 'undefined' ? getClient() : makeClient();
}

// Fetch multiple entries by content type
export async function fetchEntries(contentType: string): Promise<BlogPost[]> {
  const client = getApolloClient(); // Use the refactored function here.
  const { data } = await client.query<EntryCollectionResponse>({
    query: GET_ENTRIES_QUERY,
    variables: { contentType },
  });

  return data.entryCollection.items;
}

// Fetch a single entry by entry ID
export async function fetchSingleEntry(entryId: string): Promise<BlogPost | null> {
  const client = getApolloClient(); // And here.
  const { data } = await client.query<SingleEntryResponse>({
    query: GET_SINGLE_ENTRY_QUERY,
    variables: { entryId },
  });

  return data.pageBlogPost;
}

@phryneas
Copy link
Member

One more comment here:

export function makeClient() {
  // Adjust to use NextSSRApolloClient for SSR with appropriate caching and link setup
  return new NextSSRApolloClient({
    link: typeof window === "undefined"
      ? ApolloLink.from([new SSRMultipartLink({ stripDefer: true }), httpLink])
      : httpLink,
-    cache: new InMemoryCache(),
+   cache: new NextSSRInMemoryCache()
  });
}

Apart from that, I think at this point you're set up correctly :)

I think your questions here are solved, so I'm closing this issue.

Generally, if you have usage questions like this, please consider asking in the Apollo GraphQL Discord first - the issue tracker here is meant more for bug reports.

Copy link
Contributor

Do you have any feedback for the maintainers? Please tell us by taking a one-minute survey. Your responses will help us understand Apollo Client usage and allow us to serve you better.

Copy link
Contributor

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
For general questions, we recommend using StackOverflow or our discord server.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants