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
358 stars 25 forks source link

SSR Method Server-side rendering with Javascript disabled? #270

Closed sgup closed 1 month ago

sgup commented 1 month ago
Package Versions:
next: 14.1.0
@apollo/client: 3.9.10
@apollo/experimental-nextjs-app-support: 0.9.1

Hi,

I'm using Nextjs with the App router, and have followed the instructions for incorporating the SSR method from this library. I'm also using it with the useSuspenseQuery hook. Is it possible to have the page render right away, even with JS disabled? As soon as I introduce this hook, all I see is the loading.ts content, even on reloads where it seems to have cached the content server-side.

In my testing it works perfect fine when using the RSC method and passing in the data, so the client components can render fine on the server. Id just like to be able to use SSR/useSuspenseQuery to have proper hydration/client-side cache-and-network to have the latest data.

Here's my code:

// apollo/ApolloWrapper.tsx
'use client';
declare module '@apollo/client' {
  export interface DefaultContext {
    token?: string;
  }
}

import { ApolloLink, HttpLink } from '@apollo/client';
import { setContext } from '@apollo/client/link/context';
import {
  ApolloNextAppProvider,
  NextSSRInMemoryCache,
  NextSSRApolloClient,
  SSRMultipartLink,
} from '@apollo/experimental-nextjs-app-support/ssr';
import typePolicies from './typePolicies';

const uri = `${process.env.NEXT_PUBLIC_GRAPHQL_URL}/graphql`;

// have a function to create a client for you
function makeClient() {
  const authLink = setContext(async (_, { headers, token }) => {
    return {
      headers: {
        ...headers,
        ...(token ? { authorization: `Bearer ${token}` } : {}),
      },
    };
  });

  const httpLink = new HttpLink({
    // this needs to be an absolute url, as relative urls cannot be used in SSR
    uri,
    // you can disable result caching here if you want to
    // (this does not work if you are rendering your page with `export const dynamic = "force-static"`)
    // fetchOptions: { cache: 'no-store' },
  });

  return new NextSSRApolloClient({
    cache: new NextSSRInMemoryCache({
      typePolicies,
    }),
    link:
      typeof window === 'undefined'
        ? ApolloLink.from([
            new SSRMultipartLink({
              stripDefer: true,
            }),
            authLink.concat(httpLink),
          ])
        : authLink.concat(httpLink),
  });
}

// you need to create a component to wrap your app in
export function ApolloWrapper({ children }: React.PropsWithChildren) {
  return (
    <ApolloNextAppProvider makeClient={makeClient}>
      {children}
    </ApolloNextAppProvider>
  );
}
// app/layout.tsx
export default function RootLayout({ children }: { children: ReactNode }) {
  return (
    <html lang="en">
      <body className={classNames(subFont.className, 'bg-white')}>
        <ApolloWrapper>
          <AppProviders>
            <HeaderWrapper>{children}</HeaderWrapper>
          </AppProviders>
        </ApolloWrapper>
      </body>
    </html>
  );
}
// app/content/[id]/page.tsx
export default async function Page({ params }: Props) {
  const id = params.id
  return <ContentPage id={id} />;
}
// app/content/[id]/loading.tsx
export default async function Loading() {
  return <div>Loading...</div>
}
export default function ContentPage({
  id,
}: {
  id: number;
}) {
  const { data } = useSuspenseQuery(GetContentDocument, {
    variables: {
      id,
    },
    fetchPolicy: 'cache-and-network',
  });

  return <div>{data?.content?.title}</div>
}
phryneas commented 1 month ago

It would work if your browser were a search engine, because in that case, Next.js renders the whole page on the server and flushes it when it is finished - but in the case of normal browsers, Next.js uses React's "out of order" streaming and that requires JS to be enabled.

Your page will still load with the .js files 100% blocked, but there is a little bit of inline JS required to "stitch it together" correctly, because it it streaming over parts of the page as they finish rendering on the server, not in the order they actually appear in the DOM.

sgup commented 1 month ago

It would work if your browser were a search engine, because in that case, Next.js renders the whole page on the server and flushes it when it is finished - but in the case of normal browsers, Next.js uses React's "out of order" streaming and that requires JS to be enabled.

Ok, good to know! Search engines/SEO is my main concern, so it seems like the next/vercel magic will do that with this lib. Thanks for all your expertise and great work on this library!

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.