wawoon blog

Relayで見るNext.jsとSSGの未来

現在自分が取り組んでいるプロジェクトでは、Next.jsとRelay Modernを採用して開発を進めています。 RelayはFacebookが開発しているGraphQLクライアントライブラリです。 Next.jsの9.3で導入されたgetStaticProps, および9.4で導入されたIncremental Static Regenerationは、Relayと非常に相性がいい、ということを説明します。

Example

公式のexampleがすでにあるので見てください。 https://github.com/vercel/next.js/tree/canary/examples/with-relay-modern

以下が典型的なpagesの構成です。

pages/index.js

import Link from 'next/link'
import { fetchQuery } from 'react-relay'
import { initEnvironment } from '../lib/relay'
import BlogPosts from '../components/BlogPosts'
import indexPageQuery from '../queries/indexPage'
 
const Index = ({ viewer }) => (
  <div>
    <Link href="/about">
      <a>About</a>
    </Link>
    <BlogPosts viewer={viewer} />
  </div>
)
 
export async function getStaticProps() {
  const environment = initEnvironment()
  const queryProps = await fetchQuery(environment, indexPageQuery)
  const initialRecords = environment.getStore().getSource().toJSON()
 
  return {
    props: {
      ...queryProps,
      initialRecords,
    },
  }
}
 
export default Index

pages/_app.js

import { ReactRelayContext } from 'react-relay'
import { useEnvironment } from '../lib/relay'

export default function App({ Component, pageProps }) {
  const environment = useEnvironment(pageProps.initialRecords)

  return (
    <ReactRelayContext.Provider value={{ environment, variables: {} }}>
      <Component {...pageProps} />
    </ReactRelayContext.Provider>
  )
}

ポイント

1.getStaticProps内でfetchQueryを行っており、Reactコンポーネント内ではデータを受け取っていること

  • これは後述しますが、Next.jsのSSG化のためにはすべてをjsonとしてキャッシュする必要があるため必要な処理です。
  • 通常であればQueryRendererなどを利用してReact内でローディング状態をハンドリングしながらレンダリングします

2.getStaticProps内でinitialRecordsを返しており, _app.jsx内でenvironmentを復元していること

  • Relayではfragmentの内容はfetchQueryの返り値の中には含まれておらず、fragmentの内容は実際にはenvironmentのなかに入っています
  • initialRecordsをgetStaticPropsで送ることで、クライアントサイドで全pagesで共通で実行される_appで受け取り、environmentを復元することでfragment機能を壊さないようしています。
  • fragmentを利用しないならばこの処理は必要ありませんが、利用するならば必須です。

なぜこの選定がよいのか?

Next.jsとSSG

Incremental Static Regeneration で実現する次世代のサーバーアーキテクチャ にあるように、Next.js 9.4ではIncremental Static Regenerationが実装されています。

getStaticPropsで返す値にunstable_revalidateフィールドを追加すると、Next.jsのデプロイ後に定期的に特定ページをSSGし直し、静的ページを配信できるようになります。 これにより、ある程度動的なコンテンツであっても、ほぼの最新状態をSSG化し配信することが可能になります。

よくある記事メディアや、ログイン機能がないようなサービスはSSG化することで、lighthouseの点数を上げることができます。 また、Incremental Static Regenerationにより、ampを生成することももちろん可能です(一部バグ(1,2)がありますが回避可能です。)

SSGとGraphQL

SSGとGraphQLは非常に相性がいいです。(GatsbyはGraphQLでクエリしたデータをベースにSSGしています) GraphQLの欠点の一つは1つのクエリに対して複数のフィールドがクエリされるため、最も遅いフィールドがボトルネックになることですが、Incremental Static Regenerationによりこの欠点は解消されます。

GraphQLライブラリの中でもRelayを使うべき理由

上記の利点はNext.jsとApolloを採用しても同じ結果を得られますが、個人的にはRelayをおすすめします。

はじめから全部入りであること

Relayはbuilt-inでrelay-compilerというgraphqlクエリを事前にcompileしてAST化するツールが入っています。 これにはtypescriptプラグインがあり、これをいれるとレスポンスの値は常に型がついた状態になります。

Apolloの場合でもgraphql-code-generatorというツールをつかっても同じことができますが、relayはすべて公式で提供されているので設定が楽です。

Fragmentの利用が快適であること

Relayはfragmentをはじめから使いやすくするための仕組みが入っています。 デフォルトでsrc以下にあるコンポーネントをcreateFragmentContainerでラップすると、他のクエリでfragmentを使えるようになります。 また、fragmentでラップされている側のコンポーネントでは、fragment内で指定されているフィールド以外は見ることができず、安全であり、もちろんTypeScriptの型も自動で生成されます。(apolloの場合はgraphql-anywhereでフィルタする必要があります。)

自分の観測では、Apolloユーザーの方たちはfragmentを活用するのに苦労している人たちが多い印象です。fragmentを使いたい人はRelayをおすすめします。

Next.jsで使うGraphQLライブラリはNodeで実行されるのであまり変わらない

Next.js/SSGを念頭に置くと、useQueryやQueryコンポーネントなどの機能は使わなくなります(relayにもhooksサポートはあります)。 というのも、getStaticPropsや、getServerSidePropsはnodeで実行され、返り値のみがReactの世界に送出されます。 したがってhooksなどを利用するのはクライアント側のみで実行結果を得たいときのみになります。

基本的に、クライアント側で取得する必要のないデータ取得はSSGに寄せるのが良いです。 getStaticPropsをつかってSSGされているページは、next/linkのprefetch機能により、ページ内にリンクが表示された瞬間にjsonを事前ダウンロードされ、遷移時はネットワークリクエストなしで遷移できるので非常に速いです。 ユーザーアクセスが多い場合でもサーバーリクエストはunstable_revalidateで間引きされるので、そこまで増えないのでそこもメリットです。

GraphQLに関する相談をしたい人はこちら

もしNext.jsでRelayをつかうかも、と思われる方がいれば以下のSlackに入ってもらえると、自分も質問対応できるのでおすすめです。

GraphQLを使っている人たちの集まり

執筆時点で175名の方がいます。各ライブラリごとにchannelを切っているので、ライブラリ固有の相談などもできます。

まとめ