Open3
Server Components で GraphQL のデータを取得するやり方を模索中

概要
タイトルのままだが、
Server Components の await と Suspense を使って、GraphQL からデータを取得する
未消化
- Suspense であるが、data が optional
- graphqlClient を シングルトンにした方が良い?
- @0no-co/graphqlsp で schema.ts を取得したいが、 backend auth で止まって、introspection が取得できない

技術的には、以下を想定
frontend
- Next.js with server actions
- fetch with Session Cookie

候補
前提として、導入 | gql.tada で gql.tada を進めていた。
ドキュメント先で urql の useQuery を使っていたが、Server Components で取得する方法がないので、調査した。
結論として、以下が良さそう。
urql の query().toPromise() メソッドを利用する
理由は、async Server Components と相性が良さそうだったため。
(もうちょっと試行錯誤が必要そう)
対策
src/app/test/page.tsx
import { Child } from '@/app/test/_components/Child/_.tsx'
import type { NextPage } from 'next'
import { Suspense } from 'react'
import type { Simplify } from 'type-fest'
type Props = Simplify<Record<string, unknown>>
const Page = (async () => {
return (
<main>
Page
<div>
<Suspense fallback={'取得中'}>
<Child />
</Suspense>
</div>
</main>
)
}) satisfies NextPage<Props>
// biome-ignore lint/style/noDefaultExport: App Router の仕様
export default Page
src/app/test/_components/Child/_.tsx
import { graphql } from '@/_abstract/libs/gql-tada/config/graphql'
import { graphqlClient } from '@/_abstract/libs/urql/config/client'
import type { FC } from 'react'
import type { Simplify } from 'type-fest'
type Props = Simplify<Record<string, unknown>>
const healthCheckQuery = graphql(`
query healthCheck {
healthCheck {
status
}
}
`)
export const Child = (async () => {
const result = await graphqlClient.query(healthCheckQuery, {}).toPromise()
// biome-ignore lint/suspicious/noConsoleLog: TODO デバッグ後消す
console.log(result.data?.healthCheck.status)
return (
<>
tomato
{/* HERE: 取得中 -> OK と出た */}
{result.data?.healthCheck.status}
</>
)
}) satisfies FC<Props>
設定ファイル
src/_abstract/libs/urql/config/client.ts
import { serverEnv } from '@/_abstract/libs/t3-env/config/serverEnv'
import { Client, cacheExchange, fetchExchange } from '@urql/core'
/**
* GraphQL クライアント
*
* TODO: data を required にしたい
*
* @example
* ```ts
* const testQuery = `
* query testQuery($id: ID!) {
* user(id: $id) {
* id
* name
* }
* }
* `
*
* const result = await graphqlClient.query(testQuery, { id: 'uuid' }).toPromise()
* console.log(result.data?.user.id)
* ```
*/
export const graphqlClient = new Client({
url: new URL('/graphql', serverEnv.SERVER_ORIGIN).href,
exchanges: [cacheExchange, fetchExchange],
suspense: true,
// fetchOption で headers.cookies を指定したりするかも
})
src/_abstract/libs/gql-tada/config/graphql.ts
import type { introspection } from '@/_abstract/libs/gql-tada/__generated__/graphql-env.d.ts'
import { initGraphQLTada } from 'gql.tada'
/**
* 型安全な GraphQL クエリを生成するための関数
*
* @example
* ```ts
* const testQuery = `
* query testQuery($id: ID!) {
* user(id: $id) {
* id
* name
* }
* }
* `
*
* // const result = await graphqlClient.query(testQuery, { id: 'uuid' }).toPromise()
* // console.log(result.data?.user.id)
* ```
*/
export const graphql = initGraphQLTada<{
introspection: introspection
}>()
export type { FragmentOf, ResultOf, VariablesOf } from 'gql.tada'
export { readFragment } from 'gql.tada'
参考
-
https://gql-tada.0no.co/
- =>
NODE_TLS_REJECT_UNAUTHORIZED=0 pnpm gql.tada generate-output
- =>
-
https://scrapbox.io/wwwy-dev/Next.js_app_router_x_graphql
- => urql を採用 -
https://reacthustle.com/blog/how-to-set-up-graphql-urql-client-with-next-js-13-server-components?expand_article=1
- => query().toPromise() を採用