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. diff --git a/src/getStaticApolloProps.tsx b/src/getStaticApolloProps.tsx new file mode 100644 index 0000000..2fabd43 --- /dev/null +++ b/src/getStaticApolloProps.tsx @@ -0,0 +1,86 @@ +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, + isLocaleDomain: false, + isPreview: false +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +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, + clearCacheOnPageEntry: true + }, + revalidate + } + } +} + +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 }; 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 (