From cb026efbd3c626d5198fcc522bdd2e8537cf6084 Mon Sep 17 00:00:00 2001 From: Chris Bala Date: Wed, 12 May 2021 00:14:19 -0700 Subject: [PATCH 1/5] Drop in getStaticApolloProps from next-apollo-ts --- src/getStaticApolloProps.tsx | 81 ++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 src/getStaticApolloProps.tsx diff --git a/src/getStaticApolloProps.tsx b/src/getStaticApolloProps.tsx new file mode 100644 index 0000000..0ecc491 --- /dev/null +++ b/src/getStaticApolloProps.tsx @@ -0,0 +1,81 @@ +import { ParsedUrlQuery } from 'querystring' + +import { ApolloClient, ApolloProvider } from '@apollo/client' +import { GetStaticProps } from 'next' +import type { NextRouter } from 'next/dist/next-server/lib/router/router' +import React from 'react' + +export type StaticApolloProps = { + apolloState: object + generatedAt: string + revalidate?: number | null +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const notImplemented = (..._args: any): any => { + throw new Error("Can't be called from a static page") +} + +const baseFakeRouter = { + route: '', + pathname: '', + asPath: '', + basePath: '', + push: notImplemented, + replace: notImplemented, + reload: notImplemented, + back: notImplemented, + prefetch: notImplemented, + beforePopState: notImplemented, + events: { + on: notImplemented, + off: notImplemented, + emit: notImplemented + }, + isFallback: false, + isReady: false +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const getStaticApolloProps = (apolloClient: ApolloClient) => < + TParams extends ParsedUrlQuery = ParsedUrlQuery +>( + Page: React.ComponentType<{}>, + { revalidate }: { revalidate?: number } = {} +): GetStaticProps => { + return async (context) => { + const { params, locales, locale, defaultLocale } = context + // https://github.com/vercel/next.js/blob/48acc479f3befb70de800392315831ed7defa4d8/packages/next/next-server/lib/router/router.ts#L250-L259 + const router: NextRouter = { + query: params as ParsedUrlQuery, + locales, + locale, + defaultLocale, + ...baseFakeRouter + } + + const { getDataFromTree } = await import('@apollo/client/react/ssr') + const { RouterContext } = await import( + 'next/dist/next-server/lib/router-context' + ) + + const PrerenderComponent = () => ( + + + + + + ) + + await getDataFromTree() + + return { + props: { + apolloState: apolloClient.cache.extract(), + generatedAt: new Date().toISOString(), + revalidate: revalidate || null + }, + revalidate + } + } +} From f588c7af50c8eb2f72c8d9f3f58f725b2afb76ec Mon Sep 17 00:00:00 2001 From: Chris Bala Date: Wed, 12 May 2021 00:15:49 -0700 Subject: [PATCH 2/5] Make exports consistent --- src/getStaticApolloProps.tsx | 4 +++- src/index.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/getStaticApolloProps.tsx b/src/getStaticApolloProps.tsx index 0ecc491..945efc6 100644 --- a/src/getStaticApolloProps.tsx +++ b/src/getStaticApolloProps.tsx @@ -37,7 +37,7 @@ const baseFakeRouter = { } // eslint-disable-next-line @typescript-eslint/no-explicit-any -export const getStaticApolloProps = (apolloClient: ApolloClient) => < +const getStaticApolloProps = (apolloClient: ApolloClient) => < TParams extends ParsedUrlQuery = ParsedUrlQuery >( Page: React.ComponentType<{}>, @@ -79,3 +79,5 @@ export const getStaticApolloProps = (apolloClient: ApolloClient) => < } } } + +export default getStaticApolloProps; diff --git a/src/index.ts b/src/index.ts index c29a918..8774e28 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ import withApollo from "./withApollo"; +import getStaticApolloProps from './getStaticApolloProps'; -export { withApollo }; +export { withApollo, getStaticApolloProps }; From e5a9094da3e4736f5d7d6f00bf29c0d47d39ca8c Mon Sep 17 00:00:00 2001 From: Chris Bala Date: Wed, 12 May 2021 00:16:16 -0700 Subject: [PATCH 3/5] Fix types --- src/getStaticApolloProps.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/getStaticApolloProps.tsx b/src/getStaticApolloProps.tsx index 945efc6..e31f0a0 100644 --- a/src/getStaticApolloProps.tsx +++ b/src/getStaticApolloProps.tsx @@ -33,7 +33,9 @@ const baseFakeRouter = { emit: notImplemented }, isFallback: false, - isReady: false + isReady: false, + isLocaleDomain: false, + isPreview: false } // eslint-disable-next-line @typescript-eslint/no-explicit-any From ca70b14607915618296ff2cf9390b9f0c956f7d4 Mon Sep 17 00:00:00 2001 From: Chris Bala Date: Wed, 12 May 2021 00:30:56 -0700 Subject: [PATCH 4/5] Update readme --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/README.md b/README.md index 87c840c..4779521 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,24 @@ export default withApollo({ ssr: true })(Page); That's it! +## Advanced +### SSG (getStaticProps): +If you want to pre-generate your page, then do the following: + +``` +export default withApollo({ ssr: false })(YourPage) +export const getStaticProps = getStaticApolloProps(YourPage) +``` + +### ISR (getStaticProps + revalidate): +If you want to pre-generate your page, but keep updating it every N seconds, then do the following: + +``` +export default withApollo({ ssr: false })(YourPage) + +// Update every 60 seconds +export const getStaticProps = getStaticApolloProps(YourPage, { revalidate: 60 }) + ## How Does It Work? Next-apollo integrates Apollo seamlessly with Next by wrapping our pages inside a higher-order component (HOC). Using a HOC pattern we're able to pass down a central store of query result data created by Apollo into our React component hierarchy defined inside each page of our Next application. From fd09b025de9f112480a00e8decc751030a0e022c Mon Sep 17 00:00:00 2001 From: Chris Bala Date: Fri, 14 May 2021 17:50:27 -0700 Subject: [PATCH 5/5] Clear Apollo cache on entry to a page that uses getStaticApolloProps --- src/getStaticApolloProps.tsx | 3 ++- src/withApollo.tsx | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/getStaticApolloProps.tsx b/src/getStaticApolloProps.tsx index e31f0a0..2fabd43 100644 --- a/src/getStaticApolloProps.tsx +++ b/src/getStaticApolloProps.tsx @@ -75,7 +75,8 @@ const getStaticApolloProps = (apolloClient: ApolloClient) => < props: { apolloState: apolloClient.cache.extract(), generatedAt: new Date().toISOString(), - revalidate: revalidate || null + revalidate: revalidate || null, + clearCacheOnPageEntry: true }, revalidate } diff --git a/src/withApollo.tsx b/src/withApollo.tsx index 2bc0daf..76cc1be 100644 --- a/src/withApollo.tsx +++ b/src/withApollo.tsx @@ -15,6 +15,7 @@ let globalApolloClient: ApolloClient | null = null; type WithApolloOptions = { apolloClient: ApolloClient; apolloState: NormalizedCacheObject; + clearCacheOnPageEntry?: boolean; }; type ContextWithApolloOptions = AppContext & { @@ -52,7 +53,7 @@ export const initOnContext = ( // Initialize ApolloClient if not already done const apolloClient = ctx.apolloClient || - initApolloClient(ac, ctx.apolloState || {}, inAppContext ? ctx.ctx : ctx); + initApolloClient(ac, ctx.apolloState || {}, inAppContext ? ctx.ctx : ctx, false); // We send the Apollo Client as a prop to the component to avoid calling initApollo() twice in the server. // Otherwise, the component would have to call initApollo() again but this @@ -82,7 +83,8 @@ export const initOnContext = ( const initApolloClient = ( acp: ApolloClientParam, initialState: NormalizedCacheObject, - ctx: NextPageContext | undefined + ctx: NextPageContext | undefined, + clearCache: boolean, ) => { const apolloClient = typeof acp === 'function' ? acp(ctx) : acp as ApolloClient; @@ -92,6 +94,8 @@ const initApolloClient = ( return createApolloClient(apolloClient, initialState, ctx); } + if (clearCache) globalApolloClient = null; + // Reuse client on the client-side if (!globalApolloClient) { globalApolloClient = createApolloClient(apolloClient, initialState, ctx); @@ -117,7 +121,7 @@ export default function withApollo(ac: ApolloClientParam) { client = pageProps.apolloClient; } else { // Happens on: next.js csr - client = initApolloClient(ac, pageProps.apolloState, undefined); + client = initApolloClient(ac, pageProps.apolloState, undefined, !!pageProps.clearCacheOnPageEntry); } return (