kk-web

Next.jsでGraphQLを扱う場合Query文字列はどこに定義すべきか

2022-09-11

現在携わっている現場において Next.js × GraphQL の組み合わせで api を呼び出しているのですが。

Next.js の場合、pages フォルダー以下に切られたファイルは勝手にルーティングがマッピングされてしまうため、Query 文字列をファイル単位で切り出すことができません。(できるかもしれないですが、個人的には抵抗感がすごい)

ということで、Query 文字列の切り出しかたについて Next.js 公式のサンプルを色々と調べてみました。


切り出さない

api-routes-graphql

...

export default function Index() {
  const { data, error } = useSWR('{ users { name } }', fetcher)
  ...
}

わかりやすいですね、個人的にもアリだと思います。

メリット・デメリットは以下のような感じでしょうか。

切り出さないメリット

  • シンプルで実装に迷わない
  • コンポーネントの外側に余計な定数を切らずに済む

切り出さないデメリット

  • Query を使い回せない
  • 意図的に同じ Query を叩く場合、保守性に欠ける

Presentational components に切り出す

with-graphql-hooks

...
import PostList, {
  ALL_POSTS_QUERY,
  allPostsQueryOptions,
} from '../components/post-list'
...

export async function getStaticProps() {
  ...
  await graphQLRequest(client, ALL_POSTS_QUERY, allPostsQueryOptions())
  ...
}

これすごいですね、大胆というかなんというか。

要するにサーバー側で叩く Query とクライアント側で叩く Query は同じで、かつキャッシュのキーとして持ちたいがためにこう定義したんだと思います。

Presentational components に切り出すメリット

  • Query 文字列が常に等しく、保守性が高い
  • Container components(以下 page components と同義)が膨らまない

presentational components に切り出すデメリット

  • Presentational components の目的から外れた定数を export する必要がある
  • Presentational components からリクエストしないケースが考慮されていない

グローバルに切り出す

with-typescript-graphql

...
import { UpdateNameDocument, ViewerDocument } from 'lib/graphql-operations'
...

export async function getStaticProps() {
  ...
  await apolloClient.query({
    query: ViewerDocument,
  })
  ...
}

...

実際にこう書いている人も多いであろうやり方です。

pages フォルダー配下へ置けないのならグローバルに置いちゃえ!という、ちょっと安易とも言えるやり方です。

グローバルに切り出すメリット

  • Container components が膨らまない
  • Query 文字列の使い回しが可能

グローバルに切り出すデメリット

  • 命名規則を決める必要がある
  • 似たような Query が生成される恐れがあり、保守性が低い
  • グローバルとして扱うべきでない定数をグローバルとして扱うことになる

page components に切り出す

with-apollo-neo4j-graphql

...

const GET_MOVIES = gql`
  query GetMovies {
    getMovies {
      title
      tagline
      released
      actors {
        name
      }
      directors {
        name
      }
    }
  }
`

export default function Home() {
  const { loading, error, data } = useQuery(GET_MOVIES)
  ...
}

個人的に、一番使われているパターンなのかなと予想しているのがこれです。

とはいえ意外とメリットも薄いような気もしますが、どうなんでしょうか。

page components に切り出すメリット

  • コンポーネントの定数の中に定義されないため見た目が良い
  • page components 内部で使い回しが可能

page components に切り出すデメリット

  • page components の外側で使い回せない(export をつければ可能だが…)
  • page components が縦に長くなる

そんな感じです、公式のサンプルだけでもこれだけ書き方が分かれているのがすごいですね。

ぶっちゃけどの記法も微妙なので、これ!という正解はないと思います。

保守性を取るか、書きやすさを取るか、見栄えを取るか、最低限統一性さえ保たれていれば良いのかなーと。

あと、今回は generator や TypeScript については考慮していません、公式のサンプルをコピペしただけなので当たり前ですが。

なので諸々の兼ね合いとバランスを考慮した上で、ルールとして導入するのが無難かなーと思います。