next.jsgraphqlapolloapollo-client

Pagination of Data with Apollo GraphQL and SSR NextJS pages


I am using Next.js v14, graphql, ApolloClient and ApolloGraphQL Experimental support for NextJS

{
    "@apollo/client": "^3.12.0-rc.3",
    "@apollo/experimental-nextjs-app-support": "^0.11.6",
}

I have been following this blog post so far on how to use ApolloClient with NextJS v13+ https://www.apollographql.com/blog/how-to-use-apollo-client-with-next-js-13.

I am trying to make a SSR front page that showcases user posts. It is paginated, such that it initially loads X amount and then loads more if the user chooses to load more.

However, I am confused on how to implement pagination. Looking at the docs on pagination https://www.apollographql.com/docs/react/pagination/core-api#the-fetchmore-function.

It's easy to follow, however, it's using useQuery from @apollo/client, which only works on csr components. Because my page is SSR, using the code from the first blog, I have to use getClient().query(), but this does not have any fetchMore() methods on the response result. I implemented a fetchMore function that I think would work, but because the page is SSR, button's can't have onClick handlers. Am I missing something? What's the solution? I want to keep the retrieval of the data on the server.

Here is my relevant code:

import { getClient } from '@/lib/apollo';
import { gql } from '@apollo/client';

const GET_ALL_POSTS = gql`
    query GetAllPosts($limit: Int!, $cursor: String) {
        posts(limit: $limit, cursor: $cursor) {
            posts {
                id
                title
            }
            hasMore
        }
    }
`;

export const revalidate = 0;

export default async function Home() {
    const queryResult = await getClient().query({
        query: GET_ALL_POSTS,
        variables: {
            limit: 10,
        },
    });

    // irrelevant code for handling loading state

    const data = queryResult.data?.posts;

    // example idea for fetching more, but can't have onClick for buttons with SSR
    async function fetchMore() {
        await getClient().query({
            query: GET_ALL_POSTS,
            variables: {
                limit: 10,
                cursor: data.posts[data.posts.length - 1].createdAt,
            },
        });

        getClient().cache.writeQuery({
            query: GET_ALL_POSTS,
            data: {
                posts: {
                    posts: [...data.posts, ...queryResult.data.posts.posts],
                    hasMore: queryResult.data.posts.hasMore,
                },
            },
        });
    }


    return (<div>
        // irrelevant code for showcasing posts
            {queryResult.data && data.hasMore && (
                <button
                    onClick={fetchMore}
                >
                    Load more
                </button>
            )}
    </div>);
}

I suppose even if I did have a fetchMore call available to me, the button would still not be able to call it since its SSR page and can't have an onClick event. So maybe just making it a CSR page is the correct thing to do. Is there no way to make all the queries run on the server?


Solution

  • Stuff like this will only work in Client Components - in your React Server components, every render starts off with a completely empty cache that isn't connected to any previous user request and, as you already noticed, you ship non-interactive HTML to the browser.

    That said, you probably do not have any real reason to use React Server Components here in the first place - just use useSuspenseQuery in a Client Component. Client Components server-side render on first page load, too, but then they will continue interactively in the browser. (The Nextjs/Apollo package you linked to will ensure correct hydration in the browser).

    That is probably what you want.