📊

Next.js + ApolloでSSRするときにパフォーマンスが低下する問題について

2020/11/27に公開

Next.js + Apollo

Next.js の公式リポジトリには Apollo を使用する際のサンプルコードがあるが、
これらを参考に SSR するとかなりパフォーマンスが悪い。
with-apollo
with-typescript-graphql

例えば、with-typescript-graphql の/pages/index.tsx を見てみる。

...
import {
  useViewerQuery,
  useUpdateNameMutation,
  ViewerDocument,
} from '../lib/viewer.graphql'
import { initializeApollo } from '../lib/apollo'

const Index = () => {
  const { viewer } = useViewerQuery().data!

  ...

  return (
    ...
  )
}

export async function getStaticProps() {
  const apolloClient = initializeApollo()

  await apolloClient.query({
    query: ViewerDocument,
  })

  return {
    props: {
      initialApolloState: apolloClient.cache.extract(),
    },
  }
}

所々コードは端折っている。
ちなみにuseViewerQuerygraphql-code-generatorなどで生成される hooks で実態は Apollo のuseQuery

これをそのまま getServerSideProps に変えて SSR するとかなりパフォーマンスが悪化する。
なぜなら Apollo のuseQueryがオーバーヘッドになるから。
(具体的には useQueryで使われている getDataFromTreeが同期的に React Tree 全体を SSR するのが原因)
参考

代替策

SSR する際には Apollo のuseQueryを使用せずにfetch API かurqlを使うと良い。

fetch API を使う場合は公式の exampleに例がある。

GitHub API + urqlの場合はこんな感じ

query SearchIssues($query: String!) {
  search(query: $query, type: ISSUE, first: 10) {
    edges {
      node {
        ... on Issue {
          id
          title
          body
        }
      }
    }
  }
}
import { GetServerSideProps } from 'next'
import { createClient } from 'urql'
// graphql-code-generatorで生成した型
import {
  SearchIssuesDocument,
  SearchIssuesQuery,
} from '../types/graphql-client-api'

type Props = {
  data: SearchIssuesQuery
}

const Issues = ({ data }: Props) => {
  return ...
}

const client = createClient({
  url: 'https://api.github.com/graphql',
  fetchOptions: () => {
    const token = process.env.GITHUB_API_KEY
    return token
      ? {
          headers: {
            Authorization: `Bearer ${token}`,
            Accept: 'application/vnd.github.packages-preview+json',
          },
        }
      : {}
  },
})

export const getServerSideProps: GetServerSideProps = async () => {
  const { data } = await client
    .query<SearchIssuesQuery>(SearchIssuesDocument, { query: 'type:issue language:go' })
    .toPromise()
  return {
    props: {
      data,
    },
  }
}

export default Issues

最後に

Next.js + Apollo でパフォーマンスが悪い場合は fetch API や urql を使ってみることをおすすめします。

Discussion