LoginSignup
40
32

More than 5 years have passed since last update.

next.js + react-apollo-hooks で SSR する

Posted at

公式の 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>
  )
}
40
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
40
32