SSR (for Next static HTML export) with hooks
See original GitHub issueIn my project I have the following in place which forces me to not use Query, and instead in to a combination of ApolloConsumer + hooks in my components, which seems to not allow for fully rendered static HTML exports from next.
Why? Because I have two GraphQL data sources and need to be able to specify the client in the component, and Query doesn’t take this parameter.
What follows is my code from the following files, in this order:
- init-apollo.js
- with-apollo-client.js
- _app.js
- Post.js
- init-apollo.js
import getConfig from "next/config";
import ApolloClient from "apollo-boost";
import { InMemoryCache } from "apollo-cache-inmemory";
import { IntrospectionFragmentMatcher } from "apollo-cache-inmemory";
import { endpoint } from "../config";
import introspectionQueryResultData from "../fragmentTypes.json";
import fetch from "isomorphic-unfetch";
let apolloClient = null;
// Polyfill fetch() on the server (used by apollo-client)
if (!process.browser) {
global.fetch = fetch;
}
const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();
const fragmentMatcher = new IntrospectionFragmentMatcher({
introspectionQueryResultData
});
const { WP_TOKEN } = publicRuntimeConfig;
function create(initialState) {
const authHeaders = {
authorization: WP_TOKEN ? `Bearer ${WP_TOKEN}` : ""
};
const wpClient = new ApolloClient({
uri: process.env.NODE_ENV === "development" ? endpoint : endpoint,
cache: new InMemoryCache({ fragmentMatcher }).restore(initialState || {}),
request: operation => {
operation.setContext({
// fetchOptions: {
// credentials: "include"
// },
authHeaders
});
}
});
return wpClient;
}
export default function initApollo(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 (!process.browser) {
return create(initialState);
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = create(initialState);
}
return apolloClient;
}
- with-apollo-client.js
import React from "react";
import initApollo from "./init-apollo";
import Head from "next/head";
import { getDataFromTree } from "react-apollo";
export default App => {
return class Apollo extends React.Component {
static displayName = "withApollo(App)";
static async getInitialProps(ctx) {
const { Component, router } = ctx;
let appProps = {};
if (App.getInitialProps) {
appProps = await App.getInitialProps(ctx);
}
// Run all GraphQL queries in the component tree
// and extract the resulting data
const apollo = initApollo();
if (!process.browser) {
try {
// Run all GraphQL queries
await getDataFromTree(
<App
{...appProps}
Component={Component}
router={router}
apolloClient={apollo}
/>
);
} 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 = apollo.cache.extract();
return {
...appProps,
apolloState
};
}
constructor(props) {
super(props);
this.apolloClient = initApollo(props.apolloState);
}
render() {
return <App {...this.props} apolloClient={this.apolloClient} />;
}
};
};
- _app.js
import App, { Container } from "next/app";
import Layout from "../components/Layout";
import { ApolloProvider } from "react-apollo";
import GlobalState from "../context/GlobalState";
import ApolloClient from "apollo-boost";
import { InMemoryCache } from "apollo-cache-inmemory";
import { secondEndpoint, secondApiToken } from "../config";
import withApolloClient from "../lib/with-apollo-client";
const createSecondClient = () => {
const headers = {
"Access-Token": secondApiToken
};
const client = new ApolloClient({
uri: secondEndpoint,
cache: new InMemoryCache(),
fetchOptions: {
credentials: "include"
},
request: operation => {
operation.setContext({
headers
});
}
});
return client;
};
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
pageProps.query = ctx.query;
return { pageProps };
}
render() {
const { Component, apolloClient, pageProps } = this.props;
const wpClient = apolloClient;
const secondClient = createSecondClient();
return (
<Container>
<ApolloProvider client={{ wpClient, secondClient }}>
<GlobalState>
<Layout>
<Component {...pageProps} />
</Layout>
</GlobalState>
</ApolloProvider>
</Container>
);
}
}
export default withApolloClient(MyApp);
- Post.js
const SinglePost = props => {
let client;
const [post, setPost] = useState({});
const [thisPostId, setPostId] = useState(null);
const [ctx, setContext] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
async function getPost() {
console.log("the client", client);
const response = await client.wpClient.query({
query: GET_POST,
variables: {
uri: props.uri
}
});
const {
data: { postBy },
error
} = response;
if (error) {
setLoading({ message: "There was an error fetching the article" });
setError(error);
}
setLoading(false);
setPostId(postBy.postId);
setPost(postBy);
}
useEffect(() => {
getPost();
}, post);
return (
<AppContext.Consumer>
{context => {
if (ctx === null) {
setContext(context);
}
const { showGlobalCommentForm } = context;
return (
// We wrap this Query in an ApolloConsumer so that we have 'client' to pass on to the LoadComments component
<ApolloConsumer>
{c => {
client = c;
So, the end result is that whereas previously when using the Query component, with only one client, when I would run next export
and get a fully rendered static HTML version of the page, now I have to wait for the fetch and render.
I have read the documentation on the front page of this project for SSR, but I can’t figure out how to include the call for getMarkupFromTree in this structure, and if I could, if that would do the trick.
Thanks for any advice.
Issue Analytics
- State:
- Created 4 years ago
- Comments:9
@paulisloud you forgot the “getMarkupFromTree” and “renderToString” import in your blog post (in the final with-apollo-client.js file).
In case anyone comes across this in the future, here is a write-up of the method I ended up using: https://www.loudnoises.us/next-js-two-apollo-clients-two-graphql-data-sources-the-easy-way/