diff --git a/app/helpers/get-display-name.js b/app/helpers/get-display-name.js
new file mode 100644
index 0000000..790fa62
--- /dev/null
+++ b/app/helpers/get-display-name.js
@@ -0,0 +1,8 @@
+/**
+ * Gets the display name of a JSX component for dev tools.
+ * @param {Object | Function} component - A JSX component.
+ * @returns {String} The component display name.
+ */
+export default function getDisplayName({ displayName, name }) {
+ return displayName ? displayName : name && name !== '' ? name : 'Unknown'
+}
diff --git a/app/helpers/init-apollo-client.js b/app/helpers/init-apollo-client.js
new file mode 100644
index 0000000..ad597d8
--- /dev/null
+++ b/app/helpers/init-apollo-client.js
@@ -0,0 +1,37 @@
+import { ApolloClient } from 'react-apollo'
+import { createNetworkInterface } from 'apollo-upload-client'
+import 'isomorphic-fetch'
+
+// Used in the browser to share a single Apollo Client instance between
+// decorated components.
+let apolloClient = null
+
+/**
+ * Creates a new Apollo Client instance.
+ * @param {Object} [initialState] - Apollo client Redux store initial state.
+ * @returns {Object} Apollo Client instance.
+ */
+const createApolloClient = initialState =>
+ new ApolloClient({
+ initialState,
+ ssrMode: !process.browser,
+ networkInterface: createNetworkInterface({
+ uri: process.env.API_URI
+ })
+ })
+
+/**
+ * Gets or creates the Apollo Client instance.
+ * @param {Object} [initialState] - Apollo client Redux store initial state.
+ * @returns {Object} Apollo Client instance.
+ */
+export default function initApolloClient(initialState) {
+ // Create a new client every server-side request so that data isn't shared
+ // between connections.
+ if (!process.browser) return createApolloClient(initialState)
+
+ // Reuse client on the client-side.
+ if (!apolloClient) apolloClient = createApolloClient(initialState)
+
+ return apolloClient
+}
diff --git a/app/helpers/with-data.js b/app/helpers/with-data.js
index b4a3d25..1c376db 100644
--- a/app/helpers/with-data.js
+++ b/app/helpers/with-data.js
@@ -1,73 +1,92 @@
-import 'isomorphic-fetch'
-import React from 'react'
-import { ApolloClient, ApolloProvider, getDataFromTree } from 'react-apollo'
-import { createNetworkInterface } from 'apollo-upload-client'
+import { Component } from 'react'
+import { ApolloProvider, getDataFromTree } from 'react-apollo'
+import Head from 'next/head'
+import initApolloClient from './init-apollo-client'
+import getDisplayName from './get-display-name'
-const ssrMode = !process.browser
-let apolloClient = null
+export default ComposedComponent => {
+ return class WithData extends Component {
+ static displayName = `WithData(${getDisplayName(ComposedComponent)})`
-function initClient(headers, initialState) {
- return new ApolloClient({
- initialState,
- ssrMode,
- networkInterface: createNetworkInterface({
- uri: process.env.API_URI
- })
- })
-}
-
-function getClient(headers, initialState = {}) {
- if (ssrMode) return initClient(headers, initialState)
- if (!apolloClient) apolloClient = initClient(headers, initialState)
- return apolloClient
-}
-
-export default Component =>
- class extends React.Component {
- static async getInitialProps(ctx) {
- const headers = ctx.req ? ctx.req.headers : {}
- const client = getClient(headers)
-
- const props = {
- url: {
- query: ctx.query,
- pathname: ctx.pathname
- },
- ...(await (Component.getInitialProps
- ? Component.getInitialProps(ctx)
- : {}))
- }
-
- if (ssrMode) {
- const app = (
-
-
-
- )
- await getDataFromTree(app)
- }
-
- return {
- initialState: {
- apollo: {
- data: client.getInitialState().data
+ /**
+ * Gets the initial props for a Next.js page component.
+ * Executes on the server for the initial page load. Executes on the client
+ * when navigating to a different route via the Link component or using the
+ * routing APIs. For either environment the initial props returned must be
+ * serializable to JSON.
+ * @see https://github.com/zeit/next.js/issues/978
+ * @see https://github.com/zeit/next.js/#fetching-data-and-component-lifecycle
+ * @param {Object} context
+ * @param {String} context.pathname - Path section of the page URL.
+ * @param {Object} context.query - Query string section of the page URL parsed as an object.
+ * @param {Object} context.req - HTTP request (server only).
+ * @param {Object} context.res - HTTP response (server only).
+ * @param {Object} context.jsonPageRes - Fetch Response (client only).
+ * @param {Object} context.err - Error encountered during the rendering, if any.
+ * @returns {Promise} Page component props.
+ */
+ static async getInitialProps(context) {
+ const initialProps = {
+ composedComponentProps: {
+ url: {
+ query: context.query,
+ pathname: context.pathname
}
- },
- headers,
- ...props
+ }
}
+
+ // If the page component has initial props, merge them in.
+ if (ComposedComponent.getInitialProps) {
+ Object.assign(
+ initialProps.composedComponentProps,
+ await ComposedComponent.getInitialProps(context)
+ )
+ }
+
+ if (!process.browser) {
+ const apolloClient = initApolloClient()
+
+ try {
+ // Recurse the component tree and prefetch all Apollo data queries to
+ // populate the Apollo Client Redux store. This allows an instant
+ // server side render.
+ // See: http://dev.apollodata.com/react/server-side-rendering.html#getDataFromTree
+ await getDataFromTree(
+
+
+
+ )
+ } catch (error) {
+ // Prevent Apollo Client GraphQL errors from crashing SSR.
+ // Handle them in components via the data.error prop:
+ // http://dev.apollodata.com/react/api-queries.html#graphql-query-data-error
+ }
+
+ // Forget Head items found during the getDataFromTree render to prevent
+ // duplicates in the real render.
+ Head.rewind()
+
+ // Set Apollo Client initial state so the client can adopt data fetched
+ // on the server.
+ initialProps.initialState = {
+ apollo: apolloClient.getInitialState()
+ }
+ }
+
+ return initialProps
}
constructor(props) {
super(props)
- this.client = getClient(this.props.headers, this.props.initialState)
+ this.apolloClient = initApolloClient(this.props.initialState)
}
render() {
return (
-
-
+
+
)
}
}
+}