apollographql / apollo-client-nextjs

Apollo Client support for the Next.js App Router
https://www.npmjs.com/package/@apollo/experimental-nextjs-app-support
MIT License
433 stars 31 forks source link

SSR not working properly #335

Open juanstiza opened 1 month ago

juanstiza commented 1 month ago

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 commented 1 month ago

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

github-actions[bot] commented 1 month ago

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.

alessbell commented 1 month ago

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 :)

juanstiza commented 1 month ago

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!

phryneas commented 1 month ago

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.

juanstiza commented 1 month ago

@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 :)

phryneas commented 1 month ago

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.