Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Props, Params>(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<Props, Params>(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.
Expand Down
86 changes: 86 additions & 0 deletions src/getStaticApolloProps.tsx
Original file line number Diff line number Diff line change
@@ -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<any>) => <
TParams extends ParsedUrlQuery = ParsedUrlQuery
>(
Page: React.ComponentType<{}>,
{ revalidate }: { revalidate?: number } = {}
): GetStaticProps<StaticApolloProps, TParams> => {
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 = () => (
<ApolloProvider client={apolloClient}>
<RouterContext.Provider value={router}>
<Page />
</RouterContext.Provider>
</ApolloProvider>
)

await getDataFromTree(<PrerenderComponent />)

return {
props: {
apolloState: apolloClient.cache.extract(),
generatedAt: new Date().toISOString(),
revalidate: revalidate || null,
clearCacheOnPageEntry: true
},
revalidate
}
}
}

export default getStaticApolloProps;
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import withApollo from "./withApollo";
import getStaticApolloProps from './getStaticApolloProps';

export { withApollo };
export { withApollo, getStaticApolloProps };
10 changes: 7 additions & 3 deletions src/withApollo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ let globalApolloClient: ApolloClient<NormalizedCacheObject> | null = null;
type WithApolloOptions = {
apolloClient: ApolloClient<NormalizedCacheObject>;
apolloState: NormalizedCacheObject;
clearCacheOnPageEntry?: boolean;
};

type ContextWithApolloOptions = AppContext & {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<NormalizedCacheObject>;

Expand All @@ -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);
Expand All @@ -117,7 +121,7 @@ export default function withApollo<P, IP>(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 (
Expand Down