forked from benawad/lireddit
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
221 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
import React from "react"; | ||
import App from "next/app"; | ||
import Head from "next/head"; | ||
import { ApolloProvider } from "@apollo/client"; | ||
|
||
// On the client, we store the Apollo Client in the following variable. | ||
// This prevents the client from reinitializing between page transitions. | ||
let globalApolloClient = null; | ||
|
||
/** | ||
* Installs the Apollo Client on NextPageContext | ||
* or NextAppContext. Useful if you want to use apolloClient | ||
* inside getStaticProps, getStaticPaths or getServerSideProps | ||
* @param {NextPageContext | NextAppContext} ctx | ||
*/ | ||
export const initOnContext = (ac, ctx) => { | ||
const inAppContext = Boolean(ctx.ctx); | ||
|
||
// We consider installing `withApollo({ ssr: true })` on global App level | ||
// as antipattern since it disables project wide Automatic Static Optimization. | ||
if (process.env.NODE_ENV === "development") { | ||
if (inAppContext) { | ||
console.warn( | ||
"Warning: You have opted-out of Automatic Static Optimization due to `withApollo` in `pages/_app`.\n" + | ||
"Read more: https://err.sh/next.js/opt-out-auto-static-optimization\n" | ||
); | ||
} | ||
} | ||
|
||
// Initialize ApolloClient if not already done | ||
const apolloClient = | ||
ctx.apolloClient || | ||
initApolloClient(ac, ctx.apolloState || {}, inAppContext ? ctx.ctx : ctx); | ||
|
||
// 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 | ||
// time without the context. Once that happens, the following code will make sure we send | ||
// the prop as `null` to the browser. | ||
apolloClient.toJSON = () => null; | ||
|
||
// Add apolloClient to NextPageContext & NextAppContext. | ||
// This allows us to consume the apolloClient inside our | ||
// custom `getInitialProps({ apolloClient })`. | ||
ctx.apolloClient = apolloClient; | ||
if (inAppContext) { | ||
ctx.ctx.apolloClient = apolloClient; | ||
} | ||
|
||
return ctx; | ||
}; | ||
|
||
/** | ||
* Always creates a new apollo client on the server | ||
* Creates or reuses apollo client in the browser. | ||
* @param {NormalizedCacheObject} initialState | ||
* @param {NextPageContext} ctx | ||
*/ | ||
const initApolloClient = (apolloClient, initialState, ctx) => { | ||
// Make sure to create a new client for every server-side request so that data | ||
// isn't shared between connections (which would be bad) | ||
if (typeof window === "undefined") { | ||
return createApolloClient(apolloClient(ctx), initialState, ctx); | ||
} | ||
|
||
// Reuse client on the client-side | ||
if (!globalApolloClient) { | ||
globalApolloClient = createApolloClient( | ||
apolloClient(ctx), | ||
initialState, | ||
ctx | ||
); | ||
} | ||
|
||
return globalApolloClient; | ||
}; | ||
|
||
/** | ||
* Creates a withApollo HOC | ||
* that provides the apolloContext | ||
* to a next.js Page or AppTree. | ||
* @param {Object} withApolloOptions | ||
* @param {Boolean} [withApolloOptions.ssr=false] | ||
* @returns {(PageComponent: ReactNode) => ReactNode} | ||
*/ | ||
export const createWithApollo = (ac) => { | ||
return ({ ssr = false } = {}) => (PageComponent) => { | ||
const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => { | ||
let client; | ||
if (apolloClient) { | ||
// Happens on: getDataFromTree & next.js ssr | ||
client = apolloClient; | ||
} else { | ||
// Happens on: next.js csr | ||
client = initApolloClient(ac, apolloState, undefined); | ||
} | ||
|
||
return ( | ||
<ApolloProvider client={client}> | ||
<PageComponent {...pageProps} /> | ||
</ApolloProvider> | ||
); | ||
}; | ||
|
||
// Set the correct displayName in development | ||
if (process.env.NODE_ENV !== "production") { | ||
const displayName = | ||
PageComponent.displayName || PageComponent.name || "Component"; | ||
WithApollo.displayName = `withApollo(${displayName})`; | ||
} | ||
|
||
if (ssr || PageComponent.getInitialProps) { | ||
WithApollo.getInitialProps = async (ctx) => { | ||
const inAppContext = Boolean(ctx.ctx); | ||
const { apolloClient } = initOnContext(ac, ctx); | ||
|
||
// Run wrapped getInitialProps methods | ||
let pageProps = {}; | ||
if (PageComponent.getInitialProps) { | ||
pageProps = await PageComponent.getInitialProps(ctx); | ||
} else if (inAppContext) { | ||
pageProps = await App.getInitialProps(ctx); | ||
} | ||
|
||
// Only on the server: | ||
if (typeof window === "undefined") { | ||
const { AppTree } = ctx; | ||
// When redirecting, the response is finished. | ||
// No point in continuing to render | ||
if (ctx.res && ctx.res.finished) { | ||
return pageProps; | ||
} | ||
|
||
// Only if dataFromTree is enabled | ||
if (ssr && AppTree) { | ||
try { | ||
// Import `@apollo/react-ssr` dynamically. | ||
// We don't want to have this in our client bundle. | ||
const { getDataFromTree } = await import( | ||
"@apollo/client/react/ssr" | ||
); | ||
|
||
// Since AppComponents and PageComponents have different context types | ||
// we need to modify their props a little. | ||
let props; | ||
if (inAppContext) { | ||
props = { ...pageProps, apolloClient }; | ||
} else { | ||
props = { pageProps: { ...pageProps, apolloClient } }; | ||
} | ||
|
||
// Take the Next.js AppTree, determine which queries are needed to render, | ||
// and fetch them. This method can be pretty slow since it renders | ||
// your entire AppTree once for every query. Check out apollo fragments | ||
// if you want to reduce the number of rerenders. | ||
// https://www.apollographql.com/docs/react/data/fragments/ | ||
await getDataFromTree(<AppTree {...props} />); | ||
} catch (error) { | ||
// Prevent Apollo Client GraphQL errors from crashing SSR. | ||
// Handle them in components via the data.error prop: | ||
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error | ||
console.error("Error while running `getDataFromTree`", error); | ||
} | ||
|
||
// getDataFromTree does not call componentWillUnmount | ||
// head side effect therefore need to be cleared manually | ||
Head.rewind(); | ||
} | ||
} | ||
|
||
return { | ||
...pageProps, | ||
// Extract query data from the Apollo store | ||
apolloState: apolloClient.cache.extract(), | ||
// Provide the client for ssr. As soon as this payload | ||
// gets JSON.stringified it will remove itself. | ||
apolloClient: ctx.apolloClient, | ||
}; | ||
}; | ||
} | ||
|
||
return WithApollo; | ||
}; | ||
}; | ||
|
||
function createApolloClient(apolloClient, initialState, ctx) { | ||
// The `ctx` (NextPageContext) will only be present on the server. | ||
// use it to extract auth headers (ctx.req) or similar. | ||
apolloClient.ssrMode = Boolean(ctx); | ||
apolloClient.cache.restore(initialState); | ||
|
||
return apolloClient; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,37 @@ | ||
import { withApollo as createWithApollo } from "next-apollo"; | ||
import { createWithApollo } from "./createWithApollo"; | ||
import { ApolloClient, InMemoryCache } from "@apollo/client"; | ||
import { PaginatedPosts } from "../generated/graphql"; | ||
import { NextPageContext } from "next"; | ||
|
||
const client = new ApolloClient({ | ||
uri: process.env.NEXT_PUBLIC_API_URL as string, | ||
credentials: "include", | ||
cache: new InMemoryCache({ | ||
typePolicies: { | ||
Query: { | ||
fields: { | ||
posts: { | ||
keyArgs: [], | ||
merge( | ||
existing: PaginatedPosts | undefined, | ||
incoming: PaginatedPosts | ||
): PaginatedPosts { | ||
return { | ||
...incoming, | ||
posts: [...(existing?.posts || []), ...incoming.posts], | ||
}; | ||
const createClient = (ctx: NextPageContext) => | ||
new ApolloClient({ | ||
uri: process.env.NEXT_PUBLIC_API_URL as string, | ||
credentials: "include", | ||
headers: { | ||
cookie: | ||
(typeof window === "undefined" ? ctx.req?.headers.cookie : undefined) || | ||
"", | ||
}, | ||
cache: new InMemoryCache({ | ||
typePolicies: { | ||
Query: { | ||
fields: { | ||
posts: { | ||
keyArgs: [], | ||
merge( | ||
existing: PaginatedPosts | undefined, | ||
incoming: PaginatedPosts | ||
): PaginatedPosts { | ||
return { | ||
...incoming, | ||
posts: [...(existing?.posts || []), ...incoming.posts], | ||
}; | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}), | ||
}); | ||
}), | ||
}); | ||
|
||
export const withApollo = createWithApollo(client); | ||
export const withApollo = createWithApollo(createClient); |