0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

graphql-codegen の typescript-react-apollo を移行した話

0
Posted at

はじめに

ポートフォリオを、Contentful・Next.js・Apollo Client で構成し、GitHub Pages で運用しています。

Contentful の GraphQL Content API から型を生成するために、作成当初から GraphQL Code Generator(graphql-codegen)を使っており、@graphql-codegen/typescript-react-apollo プラグインで useTopQuery のような hooks を生成していました。

2026年3月に Apollo Client を v4 へアップデートした際、生成 hooks まわりでいくつかエラーが発生したため、以下のような回避策でビルドを通していました。

  • 生成ファイルの先頭に // @ts-nocheck を付けて、生成 hooks の型エラー(関数オーバーロードのエラー)を抑えた
  • Server Component から生成ファイルを import すると @apollo/client/react の読み込みでビルドが失敗したため、同じクエリを documents.ts に gql で手書きし、.graphql と二重管理にした

ライブラリのアップデートは dependabot と Claude Code ルーチンで自動化していますが、graphql のメジャーバージョンアップが typescript-react-apollo を原因に進められないことがわかりました。調べてみると、Apollo Client 4 へのアップデート時に生成 hooks から移行しておくべきだったことがわかったので、調査した内容と移行手順をまとめます。

移行する理由

生成 hooks が推奨されていない

Apollo Client 4 の移行ガイドには「Recommended: Avoid using generated hooks」という節があります。

If you use generated hooks from a tool such as GraphQL Codegen's @graphql-codegen/typescript-react-apollo plugin, you will not benefit from these improvements since the generated hooks are missing the necessary function overloads to provide critical type safety. We recommend that you stop using generated hooks immediately and create a migration strategy to move away from them.

Apollo Client 4 では hooks の型が改善されていますが、生成された hooks には必要な関数オーバーロードがないため、その恩恵を受けられないようです。// @ts-nocheck で抑えていた型エラーも、この関数オーバーロードに関するものでした。

代わりに、Apollo Client の hooks を TypedDocumentNode と一緒に直接使うことが推奨されています。

typescript-react-apollo のプラグインページにも、Apollo Client 4.0 とは互換性がないことが明記されています。

The generated hooks created by this package are no longer compatible with Apollo Client 4.0.

graphql v17 に対応していない

graphql の最新バージョンは 17.0.2 ですが、typescript-react-apollo の最新版(5.0.0)は peerDependencies が ^16.0.0 までとなっています。

Apollo Client や graphql-codegen の他のパッケージはすでに v17 に対応しており、このプラグインだけが graphql のアップデートを妨げている状態でした。

移行方法の比較

移行先を調査したところ、いくつかの方法がありました。

typescript-operations で型のみ生成する

Apollo のドキュメントで「Recommended starter configuration」として紹介されている構成です。

typescript-operations で型だけを生成し、TS ファイルに gql で書いた Document に TypedDocumentNode を手で付けます。

const GET_ROCKET_INVENTORY: TypedDocumentNode<
  GetRocketInventoryQuery,
  GetRocketInventoryQueryVariables
> = gql`
  query GetRocketInventory($year: Int!) { ... }
`

typed-document-node で型付きの Document を生成する

同じく Apollo のドキュメントで、「Generating precompiled GraphQL documents with their type definitions」として紹介されている構成です。

typed-document-node プラグインを使うと、.graphql から型付きの Document が生成されます。

gql については、モジュール実行時にパースが行われて起動時間が増えることと、返り値が DocumentNode 型でデータや変数の型を持たないことが挙げられています。生成済みの Document を使うことで、どちらも解消できます。

client-preset を使う

graphql-codegen の typescript-react-apollo のプラグインページで、移行先として推奨されている構成です。

We now recommend using the client-preset package for a better developer experience and smaller impact on bundle size.

client-preset への移行方法は、graphql-codegen の開発元のブログで紹介されています。

移行用の codemod として @eddeee888/gcg-operation-location-migration が提供されています。codemod は graphql-codegen の preset として動作し、codegen を1回実行すると、.graphql に書いた operation をコンポーネント内の graphql() へ移してくれます。

import { graphql } from './gql'

const UserDoc = graphql(`
  query User($id: ID!) {
    user(id: $id) { id name }
  }
`)

export const User = ({ id }) => {
  const { data } = useQuery(UserDoc, { variables: { id } })
}

ただし、Apollo のドキュメントでは client-preset は推奨されていません。

We do not recommend using the client preset with Apollo Client apps because it generates additional runtime code that adds bundle size to your application and includes features that are incompatible with Apollo Client.

採用した方法

今回は、typed-document-node で型付きの Document を生成する方法を採用しました。

  • 利用している Apollo Client のドキュメントに沿った構成である
  • .graphql ファイルをそのまま使えるため、クエリの書き方を変える必要がない
  • 呼び出し側の変更が useXxxQuery() から useQuery(XxxDocument) への置き換えで済む
  • 手書きしていた documents.ts を削除できる

型のみ生成する方法は .graphql から TS ファイルへクエリを移す必要があり、client-preset は Apollo で推奨されていないため、見送りました。

移行手順

パッケージの入れ替え

$ yarn remove @graphql-codegen/typescript-react-apollo @graphql-codegen/typescript
$ yarn add -D -E @graphql-codegen/typed-document-node

typescript プラグインも削除している理由は後述します。

codegen の設定変更

移行前の設定は以下のとおりです。

codegen.yml
generates:
  src/graphql/generated/graphql.tsx:
    plugins:
      - add:
          content: "// @ts-nocheck"
      - "typescript"
      - "typescript-operations"
      - "typescript-react-apollo"
    config:
      withHOC: false
      withHooks: true
      withComponents: false
      apolloReactCommonImportFrom: "@apollo/client/react"
      apolloReactHooksImportFrom: "@apollo/client/react"
      scalars:
        DateTime: string
        Dimension: number
        HexColor: string
        Quality: number

移行後の設定は以下のとおりです。

codegen.yml
generates:
  src/graphql/generated/graphql.ts:
    plugins:
      - "typescript-operations"
      - "typed-document-node"
    config:
      # Apollo Client always includes `__typename` fields
      nonOptionalTypename: true
      # Apollo Client doesn't add the `__typename` field to root types
      skipTypeNameForRoot: true
      scalars:
        DateTime: string
        Dimension: number
        HexColor: string
        Quality: number

hooks を生成しなくなったため、拡張子を .tsx から .ts に変更し、// @ts-nocheck も削除しました。nonOptionalTypename と skipTypeNameForRoot は Apollo のドキュメントで推奨されている設定です。

Apollo のドキュメントでは near-operation-file-preset と組み合わせた例が紹介されていますが、使わない場合は typescript-operations と同じ出力に typed-document-node を追加すれば良いとされているため、出力は1ファイルのままにしました。

yarn generate を実行すると、以下のような型付きの Document が生成されます。

src/graphql/generated/graphql.ts
export const TopDocument = {
  kind: 'Document',
  definitions: [ /* ... */ ],
} as unknown as DocumentNode<TopQuery, TopQueryVariables>

生成ファイルは約1,800行から764行に減りました。

コンポーネントの書き換え

生成 hooks を、Apollo Client の useQuery に置き換えます。

AboutUs.tsx
+import { useQuery } from '@apollo/client/react'
 import Container from '@components/atoms/Container'
-import { useTopQuery } from '@graphql/generated/graphql'
+import { TopDocument } from '@graphql/generated/graphql'

 const AboutUs: React.FC = () => {
   const locale = useLocale()
-  const { data } = useTopQuery({
+  const { data } = useQuery(TopDocument, {
     variables: {
       preview: process.env.PREVIEW === 'true',
       locale: locale,
       authorId: String(process.env.AUTHOR_ID),
     },
   })

data は TopQuery | undefined、variables は TopQueryVariables として推論されます。

手書きの Document の削除

SSG 時にデータを取得する処理でも、生成された TopDocument を使うように変更しました。

src/client/index.tsx
 import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client'
-import { TopDocument } from '@graphql/documents'
-import type { TopQuery } from '@graphql/generated/graphql'
+import { TopDocument, type TopQuery } from '@graphql/generated/graphql'

 export async function prefetchTopData(locale: string) {
   const client = makeClient()
-  const result = await client.query<TopQuery>({
+  const result = await client.query({
     query: TopDocument,

生成ファイルが hooks を含まなくなり、@apollo/client/react を import しなくなったため、Server Component から生成ファイルを直接 import できるようになりました。これにより、.graphql と手書きで同期していた documents.ts を削除できました。

また、Document から型が推論されるため、型引数も不要になります。

スキーマの型の扱い

typescript-operations v6 では、Apollo の推奨構成に typescript プラグインが含まれていません。v5 までの設定例では typescript と typescript-operations を併用していたため、v6 での変更点となります。

typescript プラグインを削除すると、Award や Achievement といったスキーマの型が生成されなくなります。ポートフォリオではこれらの型をコンポーネントで import していたため、クエリの結果の型である TopQuery から取り出す形に置き換えました。

src/types/top.ts
import type { TopQuery } from '@graphql/generated/graphql'

type Item<T extends { items: Array<unknown> } | null> = NonNullable<
  NonNullable<T>['items'][number]
>

export type Achievement = Item<TopQuery['achievements']>
export type Award = Item<TopQuery['awards']>
export type WorkExperience = Item<TopQuery['workExperiences']>
export type AcademicBackground = Item<TopQuery['academicBackgroundCollection']>
Awards.tsx
-import { type Award, useTopQuery } from '@graphql/generated/graphql'
+import type { Award } from '@custom_types/top'
+import { TopDocument } from '@graphql/generated/graphql'

スキーマ全体の型ではなく、クエリで取得したフィールドだけの型を使うようになったため、取得していないフィールドを参照するミスも型で防げるようになりました。

CI の修正

生成ファイルが最新かを確認する GitHub Actions のワークフローがあるため、対象のファイル名を graphql.tsx から graphql.ts に変更しました。

おわりに

Apollo Client 4 へのアップデート時に残していた対応として、typescript-react-apollo から typed-document-node へ移行しました。

  • Apollo Client 4 では生成 hooks の使用は推奨されておらず、TypedDocumentNode と Apollo Client の hooks を直接使うことが推奨されている
  • 生成 hooks をやめたことで、// @ts-nocheck と手書きの documents.ts を削除できた
  • typescript-operations v6 で typescript プラグインを削除すると、スキーマの型が生成されなくなる

Apollo Client 4 へのアップデートは Claude Code で対応していました。ビルドが通ることを優先した結果、// @ts-nocheck や手書きの Document が残る形になっていたと考えます。移行ガイドの推奨事項まで確認するよう、スキルを改善する余地がありそうです。

メジャーバージョンアップでは、ビルドが通っていても本来の移行が済んでいないことがあります。対応するのが人であっても AI であっても、移行ガイドと照らし合わせて確認していきたいと思います。

参考

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?