apollo-client-nextjs icon indicating copy to clipboard operation
apollo-client-nextjs copied to clipboard

SSR not working properly

Open juanstiza opened this issue 1 year ago • 7 comments

Hi all!

I'm having a strange issue while setting up SSR. I haven't found this problem being described anywhere else. Any help will be appreciated!

What I expect:

Loading the page in the browser without javascript should render the correct content.

What I get:

The page renders only the fallback. If I load the page again without restarting the server, I can see the content.

Dependencies

react: ^18, installed: 18.2.0 @apollo/client: ^3.10.8, installed: 3.10.8 @apollo/experimental-nextjs-app-support: ^0.11.2, installed: 0.11.2 graphql: ^16.9.0, installed: 16.9.0

Setup

Apollo provider

// ApolloPrpovider.tsx
'use client';
import { ApolloLink, HttpLink } from '@apollo/client';
import {
  ApolloClient,
  ApolloNextAppProvider,
  InMemoryCache,
  SSRMultipartLink,
} from '@apollo/experimental-nextjs-app-support';
import React from 'react';

function makeClient() {
  const httpLink = new HttpLink({
    uri: 'https://myapi.com/graphql',
  });

  return new ApolloClient({
    cache: new InMemoryCache(),
    ssrMode: true,
    link:
      typeof window === 'undefined'
        ? ApolloLink.from([
            new SSRMultipartLink({
              stripDefer: true,
            }),
            httpLink,
          ])
        : httpLink,
  });
}

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

Page component

// src/app/product/page.tsx
import React from 'react';
import { MyPage } from '@/app/components/ProductPage';

export default function Page({ params }: { params: { slug: string } }) {
  return <ProductPage urlSlug={params.slug} />;
}

Content component

// src/components/ProductPage.tsx
import React, { Suspense } from 'react';
import Content from '@/app/components/Product';

export const ProductPage: React.FC<{
  urlSlug: string;
}> = ({ urlSlug }) => {
  return (
    <Suspense fallback={'Loading...'}>
      <Product urlSlug={urlSlug} />
    </Suspense>
  );
};

Product component, it should render the product's title.

// src/component/Product.tsx
'use client';
import { useSuspenseQuery } from '@apollo/client';
import query from '@/feature/product.query';

export const dynamic = 'force-dynamic';

export default function Product({
  urlSlug,
}: {
  urlSlug: string;
}) {
  const { data, error } = useSuspenseQuery(query, {
    variables: {
      slug: urlSlug,
    },
  });
  const product = data.product;

  return product.title;
}

juanstiza avatar Jul 19 '24 16:07 juanstiza

Here I provide a repro. repo: https://github.com/juanstiza/apollo-client-nextjs-repro.

juanstiza avatar Jul 19 '24 18:07 juanstiza

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.

github-actions[bot] avatar Jul 19 '24 18:07 github-actions[bot]

Hi @juanstiza 👋

Your question brings up an interesting fact about React streaming SSR: it requires JavaScript on the client in order to work. With progressive hydration, after the initial HTML of your application is rendered and sent to the browser, React generates the HTML for suspending components as they receive data and sends it to the same stream along with a script tag so it can be rendered in place of the fallback that was in the initial HTML payload.

If you remove Apollo Client from your example and replace it with a different data fetching hook that does some asynchronous work you should see the same thing. I'll leave this issue open for now, let me know if you have any other questions :)

alessbell avatar Jul 22 '24 18:07 alessbell

If you remove Apollo Client from your example and replace it with a different data fetching hook that does some asynchronous work you should see the same thing. I'll leave this issue open for now, let me know if you have any other questions :)

@alessbell Indeed, we used to do it like this but using Apollo, without the new hooks and streaming.

I guess we'll keep it like that until another solution arises. Thanks for the insight!

juanstiza avatar Jul 24 '24 15:07 juanstiza

One thing to add maybe: if Next.js detects a search engine crawler, they will not show suspense fallbacks and start out-of-order-streaming, but instead wait until all data has been fetched and then flush the whole page in order, so it will not require JavaScript from the perspective of a web crawler.

The behaviour you are seeing here is reserved for real users with real browsers.

phryneas avatar Jul 24 '24 17:07 phryneas

@phryneas This is interesting! is there a way of tricking Next.js to render the page as if I were a crawler?

I'm googling it as I finish typing this :)

juanstiza avatar Jul 24 '24 17:07 juanstiza

https://github.com/vercel/next.js//blob/efcec4c1e303848a5293cef6961be8f73fd5160b/packages/next/src/shared/lib/router/utils/is-bot.ts

You'd set a user-agent accordingly.

phryneas avatar Jul 25 '24 07:07 phryneas