From 945f3b78b2720a80b913bff021d54441252975f3 Mon Sep 17 00:00:00 2001 From: Luis Alvarez D Date: Sat, 6 Jun 2020 08:04:52 -0500 Subject: [PATCH] [Examples] Move api-routes-apollo-server-and-client to SSG (#13782) Ref: https://github.com/vercel/next.js/pull/13742 --- .../README.md | 11 +- .../apollo/client.js | 162 ++++-------------- .../package.json | 22 ++- .../pages/_app.js | 12 ++ .../pages/index.js | 38 ++-- 5 files changed, 81 insertions(+), 164 deletions(-) create mode 100644 examples/api-routes-apollo-server-and-client/pages/_app.js diff --git a/examples/api-routes-apollo-server-and-client/README.md b/examples/api-routes-apollo-server-and-client/README.md index b2004003aca2b4..e83f854dbe7bd8 100644 --- a/examples/api-routes-apollo-server-and-client/README.md +++ b/examples/api-routes-apollo-server-and-client/README.md @@ -1,13 +1,8 @@ # Apollo Server and Client Example -[Apollo](https://www.apollographql.com/client/) is a GraphQL client that allows you to easily query the exact data you need from a GraphQL server. In addition to fetching and mutating data, Apollo analyzes your queries and their results to construct a client-side cache of your data, which is kept up to date as further queries and mutations are run, fetching more results from the server. +[Apollo](https://www.apollographql.com/client/) is a GraphQL client that allows you to easily query the exact data you need from a GraphQL server. In addition to fetching and mutating data, Apollo analyzes your queries and their results to construct a client-side cache of your data, which is kept up to date as further queries and mutations are run. -In this simple example, we integrate Apollo seamlessly with Next by wrapping our _pages/\_app.js_ inside a [higher-order component (HOC)](https://facebook.github.io/react/docs/higher-order-components.html). Using the 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. - -On initial page load, while on the server and inside `getInitialProps`, we invoke the Apollo method, [`getDataFromTree`](https://www.apollographql.com/docs/react/api/react-ssr/#getdatafromtree). This method returns a promise; at the point in which the promise resolves, our Apollo Client store is completely initialized. - -Note: Do not be alarmed that you see two renders being executed. Apollo recursively traverses the React render tree looking for Apollo query components. When it has done that, it fetches all these queries and then passes the result to a cache. This cache is then used to render the data on the server side (another React render). -https://www.apollographql.com/docs/react/api/react-ssr/#getdatafromtree +In this simple example, we integrate Apollo seamlessly with [Next.js data fetching methods](https://nextjs.org/docs/basic-features/data-fetching) to fetch queries in the server and hydrate them in the browser. ## How to use @@ -39,3 +34,5 @@ npm run dev yarn yarn dev ``` + +Deploy it to the cloud with [Vercel](https://vercel.com/import?filter=next.js&utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/api-routes-apollo-server-and-client/apollo/client.js b/examples/api-routes-apollo-server-and-client/apollo/client.js index 8595ed32489e19..26ae378500bdc3 100644 --- a/examples/api-routes-apollo-server-and-client/apollo/client.js +++ b/examples/api-routes-apollo-server-and-client/apollo/client.js @@ -1,137 +1,8 @@ -import React from 'react' -import Head from 'next/head' -import { ApolloProvider } from '@apollo/react-hooks' +import { useMemo } from 'react' import { ApolloClient } from 'apollo-client' import { InMemoryCache } from 'apollo-cache-inmemory' -let globalApolloClient = null - -/** - * Creates and provides the apolloContext - * to a next.js PageTree. Use it by wrapping - * your PageComponent via HOC pattern. - * @param {Function|Class} PageComponent - * @param {Object} [config] - * @param {Boolean} [config.ssr=true] - */ -export function withApollo(PageComponent, { ssr = true } = {}) { - const WithApollo = ({ apolloClient, apolloState, ...pageProps }) => { - const client = apolloClient || initApolloClient(apolloState) - return ( - - - - ) - } - - // Set the correct displayName in development - if (process.env.NODE_ENV !== 'production') { - const displayName = - PageComponent.displayName || PageComponent.name || 'Component' - - if (displayName === 'App') { - console.warn('This withApollo HOC only works with PageComponents.') - } - - WithApollo.displayName = `withApollo(${displayName})` - } - - if (ssr || PageComponent.getInitialProps) { - WithApollo.getInitialProps = async (ctx) => { - const { AppTree } = ctx - - // Initialize ApolloClient, add it to the ctx object so - // we can use it in `PageComponent.getInitialProp`. - const apolloClient = (ctx.apolloClient = initApolloClient()) - - // Run wrapped getInitialProps methods - let pageProps = {} - if (PageComponent.getInitialProps) { - pageProps = await PageComponent.getInitialProps(ctx) - } - - // Only on the server: - if (typeof window === 'undefined') { - // When redirecting, the response is finished. - // No point in continuing to render - if (ctx.res && ctx.res.finished) { - return pageProps - } - - // Only if ssr is enabled - if (ssr) { - try { - // Run all GraphQL queries - const { getDataFromTree } = await import('@apollo/react-ssr') - await getDataFromTree( - - ) - } 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() - } - } - - // Extract query data from the Apollo store - const apolloState = apolloClient.cache.extract() - - return { - ...pageProps, - apolloState, - } - } - } - - return WithApollo -} - -/** - * Always creates a new apollo client on the server - * Creates or reuses apollo client in the browser. - * @param {Object} initialState - */ -function initApolloClient(initialState) { - // 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(initialState) - } - - // Reuse client on the client-side - if (!globalApolloClient) { - globalApolloClient = createApolloClient(initialState) - } - - return globalApolloClient -} - -/** - * Creates and configures the ApolloClient - * @param {Object} [initialState={}] - */ -function createApolloClient(initialState = {}) { - const ssrMode = typeof window === 'undefined' - const cache = new InMemoryCache().restore(initialState) - - // Check out https://github.com/vercel/next.js/pull/4611 if you want to use the AWSAppSyncClient - return new ApolloClient({ - ssrMode, - link: createIsomorphLink(), - cache, - }) -} +let apolloClient function createIsomorphLink() { if (typeof window === 'undefined') { @@ -146,3 +17,32 @@ function createIsomorphLink() { }) } } + +function createApolloClient() { + return new ApolloClient({ + ssrMode: typeof window === 'undefined', + link: createIsomorphLink(), + cache: new InMemoryCache(), + }) +} + +export function initializeApollo(initialState = null) { + const _apolloClient = apolloClient ?? createApolloClient() + + // If your page has Next.js data fetching methods that use Apollo Client, the initial state + // get hydrated here + if (initialState) { + _apolloClient.cache.restore(initialState) + } + // For SSG and SSR always create a new Apollo Client + if (typeof window === 'undefined') return _apolloClient + // Create the Apollo Client once in the client + if (!apolloClient) apolloClient = _apolloClient + + return _apolloClient +} + +export function useApollo(initialState) { + const store = useMemo(() => initializeApollo(initialState), [initialState]) + return store +} diff --git a/examples/api-routes-apollo-server-and-client/package.json b/examples/api-routes-apollo-server-and-client/package.json index cdab52d8526d56..e271c23f951d4a 100644 --- a/examples/api-routes-apollo-server-and-client/package.json +++ b/examples/api-routes-apollo-server-and-client/package.json @@ -1,28 +1,26 @@ { - "name": "with-apollo", - "version": "2.0.0", + "name": "api-routes-apollo-server-and-client", + "version": "1.0.0", "scripts": { "dev": "next", "build": "next build", "start": "next start" }, "dependencies": { - "@apollo/react-common": "3.0.1", - "@apollo/react-hooks": "3.0.1", - "@apollo/react-ssr": "3.0.1", - "apollo-cache-inmemory": "1.6.3", - "apollo-client": "2.6.4", - "apollo-link-http": "1.5.15", - "apollo-link-schema": "1.2.3", - "apollo-server-micro": "2.9.0", + "@apollo/react-common": "^3.1.4", + "@apollo/react-hooks": "^3.1.5", + "apollo-cache-inmemory": "^1.6.6", + "apollo-client": "^2.6.10", + "apollo-link-http": "^1.5.17", + "apollo-link-schema": "^1.2.5", + "apollo-server-micro": "^2.14.2", "apollo-utilities": "^1.3.2", "graphql": "^14.0.2", - "graphql-tag": "2.10.1", + "graphql-tag": "2.10.3", "next": "latest", "prop-types": "^15.6.2", "react": "^16.7.0", "react-dom": "^16.7.0" }, - "author": "", "license": "ISC" } diff --git a/examples/api-routes-apollo-server-and-client/pages/_app.js b/examples/api-routes-apollo-server-and-client/pages/_app.js new file mode 100644 index 00000000000000..0345a86b23ea35 --- /dev/null +++ b/examples/api-routes-apollo-server-and-client/pages/_app.js @@ -0,0 +1,12 @@ +import { ApolloProvider } from '@apollo/react-hooks' +import { useApollo } from '../apollo/client' + +export default function App({ Component, pageProps }) { + const apolloClient = useApollo(pageProps.initialApolloState) + + return ( + + + + ) +} diff --git a/examples/api-routes-apollo-server-and-client/pages/index.js b/examples/api-routes-apollo-server-and-client/pages/index.js index 33ab4252a092d2..643c38b3dd6c12 100644 --- a/examples/api-routes-apollo-server-and-client/pages/index.js +++ b/examples/api-routes-apollo-server-and-client/pages/index.js @@ -1,7 +1,7 @@ -import { withApollo } from '../apollo/client' import gql from 'graphql-tag' import Link from 'next/link' import { useQuery } from '@apollo/react-hooks' +import { initializeApollo } from '../apollo/client' const ViewerQuery = gql` query ViewerQuery { @@ -18,19 +18,29 @@ const Index = () => { data: { viewer }, } = useQuery(ViewerQuery) - if (viewer) { - return ( -
- You're signed in as {viewer.name} and you're {viewer.status} goto{' '} - - static - {' '} - page. -
- ) - } + return ( +
+ You're signed in as {viewer.name} and you're {viewer.status} goto{' '} + + static + {' '} + page. +
+ ) +} + +export async function getStaticProps() { + const apolloClient = initializeApollo() - return null + await apolloClient.query({ + query: ViewerQuery, + }) + + return { + props: { + initialApolloState: apolloClient.cache.extract(), + }, + } } -export default withApollo(Index) +export default Index