公式の examples にないものを実装したのでメモ。
render-props ベースの https://github.com/zeit/next.js/tree/canary/examples/with-apollo では react-apollo-hooks と作りが違うので https://github.com/trojanowski/react-apollo-hooks の SSR の例を見ながら実装した。
lib/initApolloClient.js は一緒
lib/initApolloClient.ts
import {
ApolloClient,
InMemoryCache,
HttpLink,
NormalizedCacheObject
} from "apollo-boost";
import fetch from "isomorphic-unfetch";
let apolloClient: ApolloClient<any> | null = null;
// Polyfill fetch() on the server (used by apollo-client)
if (!process.browser) {
// @ts-ignore
global.fetch = fetch;
}
function createApolloClient(initialState: NormalizedCacheObject) {
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
return new ApolloClient({
connectToDevTools: process.browser,
ssrMode: !process.browser, // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
// uri: "https://api.graph.cool/simple/v1/cixmkt2ul01q00122mksg82pn", // Server URL (must be absolute)
uri: "http://localhost:3333/graphql",
credentials: "same-origin" // Additional fetch() options like `credentials` or `headers`
}),
cache: new InMemoryCache().restore(initialState)
});
}
export default function initApollo(initialState: any = {}) {
// 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 createApolloClient(initialState);
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = createApolloClient(initialState);
}
return apolloClient;
}
apollo-client のキャッシュを組み立てないといけない。なので getDataFromTree の代わりに、react-apollo-hooks/getMarkupFromTree
する。
lib/withApolloClient.ts
import React from "react";
import initApolloClient from "./initApolloClient";
import Head from "next/head";
import { renderToString } from "react-dom/server";
import { NextAppContext } from "next/app";
import { ApolloClient } from "apollo-boost";
import { getMarkupFromTree } from "react-apollo-hooks";
export default (
App: React.ComponentType<any> & { getInitialProps?: Function }
) => {
return class Apollo extends React.Component {
static displayName = "withApollo(App)";
apolloClient: ApolloClient<any>;
static async getInitialProps(ctx: NextAppContext) {
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 = initApolloClient();
if (!process.browser) {
try {
// Run all GraphQL queries
await getMarkupFromTree({
renderFunction: renderToString,
tree: (
<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: any) {
super(props);
this.apolloClient = initApolloClient(props.apolloState);
}
render() {
return <App {...this.props} apolloClient={this.apolloClient} />;
}
};
};
あとは pages/_app.js に Provider を仕込む
import React from "react";
import App, { Container } from "next/app";
import { createGlobalStyle } from "styled-components";
import withApolloClient from "../lib/withApolloClient";
import { ApolloClient } from "apollo-boost";
import { ApolloProvider } from "react-apollo-hooks";
class ApolloApp extends App<{ apolloClient: ApolloClient<any> }> {
render() {
const { Component, pageProps, apolloClient } = this.props;
return (
<Container>
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
</Container>
)
}
}
export default withApolloClient(ApolloApp);
使う
graphql は自分で実装してください。
pages/apollo-ssr-test.tsx
import React from "react";
import gql from "graphql-tag";
import { useQuery } from "react-apollo-hooks";
const GET_USERS = gql`
query {
users {
id
name
}
}
`;
export default function Apollo() {
const { loading, data } = useQuery(GET_USERS);
return (
<div>
{!loading && (
<ul>
{data.users.map((u: { id: string; name: string }) => {
return <li key={u.id}>{u.name}</li>;
})}
</ul>
)}
</div>
)
}