Closed21

Next.js × Relay

kobokobo

モチベーション

Next.jsはv12.1でGraphQLクライアントであるRelayのコンパイルをswcサポートしたが、コンパイル部分のサポートのため、実はRelayをNext.jsに組み込もうとすると色々とハードルがある
ここでは、いくつかのソリューションを試してみてそれぞれのメリットデメリットが説明できる程度にまとめられるといいな

いくつかソリューション

議論されているissue

https://github.com/facebook/relay/issues/3889
https://github.com/facebook/relay/issues/4107

kobokobo

with-relay-modern

Next.js公式のサンプル

https://github.com/vercel/next.js/tree/canary/examples/with-relay-modern

公式サンプルなので、色々と網羅していてほしいものだがいくつか微妙そうな部分があるので確認していく

良さそうな部分

  • きちんとSSRにも対応できそう(サンプルはgetStaticPropsだが、見る感じgetServerSidePropsでもいけそう
    • そもそもRelayはサーバーサイドフェッチをする公式サンプルがないため、Next.jsと組み合わせるのには良いサンプル

微妙そうな部分

  • jsで書かれている
  • usePreloadedQueryが使われていない
    • propsでページに渡している
    • サーバーサイドでフェッチはできているが、これは型解決ができないのでは?
      • relayはusePreloadedQueryをしたときに、キャッシュからデータを復元するが、それに対し型付けをしている気がするので
  • クライアントフェッチ部分が書かれていない
    • サーバーサイドでのみフェッチする仕組みになっているが、ポーリングしたい部分等、一部クライアントフェッチしたい部分というのは出てくる
      • おそらくそちらは公式のusePreloadedQueryを使ってしまえばいけるんだと思うが、あまりにもサンプルが簡素なのでその辺りは自分で模索する必要あり
  • Suspenseが用いられていない
    • relayではuseLazyLoadQueryが使用されるコンポーネントをSuspenseで囲むことで遅延読み込みを可能にしているが、こちらのサポートがされていなさそうに見える
    • 他にもrelayはfragmentで@deferを用いた場合、データの遅延ロード&Suspenseによる遅延表示を実現しているがこちらもサポートしていなさそう
      • そもそも@deferにサーバーサイドが対応していなければ関係のない話なのでそこまで大きな話ではないのかもしれないし、サーバーサイドでpreloadした場合、@deferしたデータがどこに届くのかよくわかっていないので、これに関してはそこまで問題ではなさそうな気がする、、(わからない
  • バーション追従などは適宜行われているものの、基本コンセプトは3年前からほぼ変わっていない
kobokobo

tsに書き換えようと思ってやってみたけど、結構しんどい、、

relay-compilerのバージョンも古くて、tsに書き換えたときにcompileがうまく通らない。。(フラグメント周りがうまく処理できない。。

一旦、良さそうなソリューションでもないので、また時間があったら検証

kobokobo

relay-nextjs

おそらく一番進んだソリューション

https://github.com/RevereCRE/relay-nextjs

良さそうな部分

  • usePreloadedQueryやuseLazyLoadQuery等、標準のAPIを使用可能
  • サーバーサイドのプリロード、クライアントサイドでのフェッチもサポートしている
  • useLazyLoadQueryのSuspense対応もできている
  • おそらくtsサポートもされていそう

微妙そうな部分

  • サーバーサイドの大幅なカスタマイズが必要
  • _app.tsxや_document.tsxも大幅な変更をする必要がある
  • このissueによるとSSR中にクリティカルなリクエストを待つことができないらしい
    • クリティカルなリクエストとは?(わからないけど、サーバーサイドを拡張してるからつらみがあるのかな?
  • このissueによるとSG(SSG)のサポートがない
  • このissueによると並行レンダリングのサポートがない
    • いわゆる@deferとSuspenseを組み合わせた遅延レンダリングの部分な気がするけど、よくわかってない
kobokobo

Contributing
If you'd like to contribute to this package, developing locally is simple.
Prerequisites: Yarn v1 (v2 is not yet tested)
Run yarn from the root of this package
Start the live reloader for the package cd relay-nextjs && yarn build -w
In a separate terminal strt the example app: cd example && yarn dev
Now, when you make any changes in relay-nextjs, they will instantly be reflected in the example app.

となってるが、、動かないんだが、、

Server Error
TypeError: _interopRequireDefault is not a function

This error happened while generating the page. Any console logs will be displayed in the terminal window.
kobokobo

relay-runtimeのバージョン的な問題な気がするけど、、(なんかあまりメンテされてない感じがしてどうもやる気が起きない。。

kobokobo

SSR中にクリティカルなリクエストを待つことができないらしい
クリティカルなリクエストとは?(わからないけど、サーバーサイドを拡張してるからつらみがあるのかな?

の内容がわかったかもしれない。

以下はサンプルコード

export default withRelay(UserProfile, UserProfileQuery, {
...
  serverSideProps: async (ctx) => {
    const { getTokenFromCtx } = await import('lib/server/auth');
    const token = await getTokenFromCtx(ctx);
    if (token == null) {
      return {
        redirect: { destination: '/login', permanent: false },
      };
    }

    return { token };
  },
  createServerEnvironment: async (
    ctx,
    { token }: { token: string }
  ) => {
    const { createServerEnvironment } = await import('lib/server_environment');
    return createServerEnvironment(token);
  },
});

このサンプルでは、serverSidePropsでトークンを入手し、returnする。
それをcreateServerEnvironementで受け取ってenvironmentを作り出す。
withRelayにデータフェッチが内包されているため、取得したrelayのデータ(=クリティカルなリクエスト)を用いて何かすることができない

という意味かな?

まぁそんなことする必要ないでしょ?(そのままコンポーネントに渡されて、使うだけなんだから)
というのはわかるけど、サーバーサイドでそのままデータ直接参照したい場合ってないわけではないからそういうことかな?

kobokobo

SG(SSG)のサポートがない

これは確かになさそう(オプションが見当たらない)

kobokobo

並行レンダリングのサポートがない

これに関しては知識不足で検証方法がわからないが、まぁ高度な話だと思うので、特段これ以上調べない。

ちなみに、useLazyLoadQueryに関してはサポートされてるっぽいので、クライアントフェッチはできるはず

kobokobo

評価: △

完成度は高いが、relay-nextjsへの依存度が高めのコードが出来上がってしまう印象

  • TypeScirptがちゃんとサポートされている
  • usePreloadedQuery等のデフォルトが使える

というのはGood

kobokobo

next-relay-bridge

開発がストップしてそうなのでスキップ

kobokobo

data-driven-dependencies

Relayの中の人が書いてくれたサンプル

https://github.com/relayjs/relay-examples/tree/main/data-driven-dependencies

上記はFlowで書かれているが、TypeScriptで書き直してくれた人がいるので以下のURLをベースに

https://github.com/jantimon/next-relay-demo

良さそうな部分

  • TypeScriptサポート
  • getServerSidePropsを拡張することなく作れる
  • 並行レンダリングのサポートがあるっぽい
  • SGだろうがSSRだろうが使えそう
  • usePreloadedQueryを使ったサンプル
    • useLazyLoadQueryに関してはサンプルがないが、Suspense対応しているためできる気がする

微妙そうな部分

  • サーバーサイドでプリロードしたデータをクライアントサイドキャッシュに詰め込むときにかなり無理をしている
  • ライブラリ群(ハイドレート部分等)が自分達で実装になるため、基本コピペで良いだろうが、メンテナンスを自分達でする必要あり
  • クライアントサイドフェッチのサンプルがなさそう
    • クライアントサイドも考慮されてそうだけど、サンプルがなさそう
kobokobo

無理している部分について

セットしている部分

https://github.com/jantimon/next-relay-demo/blob/main/data/ReactRelayContainer.tsx#L35-L36

取り出し部分

https://github.com/jantimon/next-relay-demo/blob/main/data/network.ts#L28-L30

実際にenvironment.getNetwork()をconsole.logしてみると以下のようなものが出てくる

{responseCache: RelayQueryResponseCache, execute: ƒ}

responseCacheオブジェクトが入っているが、privateなapiっぽい(多分、クライアントサイドで扱う分には、usePreloadedQueryとかのAPIが内部的にアクセスするだけなので、外に出す必要がないということだと思う

kobokobo

クライアントサイドフェッチのサンプルがなさそう(useLazyLoadQuery)に関して

getStaticPropsの部分を削除し、useLazyLoadQueryを使ったらちゃんと実行することができた

import { PreloadedQuery } from "react-relay";
import { graphql, useLazyLoadQuery } from "react-relay/hooks";
import { Flag } from "../src/components/Flag";
import { Pasta_Query } from "../__generated__/Pasta_Query.graphql";

const Pasta = () => {
  const data = useLazyLoadQuery<Pasta_Query>(graphql`
    query Pasta_Query($countryCode: ID!) {
      country(code: $countryCode) @required(action: THROW) {
        name
        ...Flag_icon
      }
    }
  `, { countryCode: 'IT' });
  return (
    <main>
      <h1>{data.country.name}</h1>
      <Flag country={data.country} />
    </main>
  );
};

// export async function getServerSideProps() {
//   return {
//     props: {
//       preloadedQueries: {
//         query: await getPreloadedQuery(preloadQuery, {
//           countryCode: "IT",
//         }),
//       },
//     },
//   };
// }

export default Pasta;
kobokobo

ライブラリ群(ハイドレート部分等)が自分達で実装になるため、基本コピペで良いだろうが、メンテナンスを自分達でする必要ありという部分

色々見ていくと、無理やりカスタマイズしているところはあるものの、全体的に見ると割とシンプルになっているし、メンテナンスが多く発生するところでもないので良さそう

kobokobo

評価: ○

思っていたより完成度が高く、辛いポイントがlibs配下にしまえそうなので、良さそう

  • TypeScirptがちゃんとサポートされている
  • usePreloadedQuery等のデフォルトが使える
  • 最低限の_app.tsxとかの拡張に止まるので、負債を中にしまい込んでしまえば標準機能で綺麗に作っていけそう
  • getServerSideProps等の拡張も必要ない
kobokobo

responseCache部分について

無理やりなんとかしてる部分について、よりうまくできないか考えてみる

何をしているのか?

1. サーバーでプリロードする

https://github.com/jantimon/next-relay-demo/blob/main/pages/Pasta.tsx#L30-L40

2. Relayコンテナに1を渡す

https://github.com/jantimon/next-relay-demo/blob/main/pages/_app.tsx#L7

3 environmentを作成する

3.1 relayのenvironmentを作成する

https://github.com/jantimon/next-relay-demo/blob/main/data/ReactRelayContainer.tsx#L18

3.2 3.1と同時にnetworkというものを同時に作成する

https://github.com/jantimon/next-relay-demo/blob/main/data/relayEnvironment.tsx#L9

3.3 3.2と同時にデータを格納しておくcacheの箱を作成

https://github.com/jantimon/next-relay-demo/blob/main/data/network.ts#L6-L9

3.4 3.3で生成した箱を3.1で作ったnetworkに無理やり格納しておく

https://github.com/jantimon/next-relay-demo/blob/main/data/network.ts#L30

3.3で用意した箱はあくまでも、classを呼び出しているだけなので、networkに対して無理やり箱をつくりあげている

3.5 最終的にはnetworkも3.1で作ったenvironmentに格納してあげる

https://github.com/jantimon/next-relay-demo/blob/main/data/relayEnvironment.tsx#L22

以上で、キャッシュを入れておく箱の準備完了

4. データを整形

https://github.com/jantimon/next-relay-demo/blob/main/data/ReactRelayContainer.tsx#L21-L32

5. 3で作ったenvironment>network>responseCacheに対して、4を突っ込む

https://github.com/jantimon/next-relay-demo/blob/main/data/ReactRelayContainer.tsx#L33-L36

6. 最終的にコンポーネント側が知りたいのはデータではなく、データのありか(ref)なので、それをpropsとして整形して返してあげる

https://github.com/jantimon/next-relay-demo/blob/main/data/ReactRelayContainer.tsx#L38-L46

kobokobo

クライアントサイドフェッチのサンプルがなさそう(useLazyLoadQuery)に関しての続き

useLazyLoadQueryを実行した時、Suspenseのfallbackが実行されるだろう。と思い、実行

結果、SSRではFallbackが実行され、クライアントサイドでは実行されず。
SSR側でSuspenseを待ち合わせ、出来上がったDOMを渡す?

でも、クライアントサイドでもデータのフェッチは行ってそう

よくわからない。(Suspenseの知識が足りていないのか

kobokobo

ということで、現時点ではdata-driven-dependenciesによる解決策が一番良さそう

なんかまだ見えていない課題がありそうな気がするけど、、

小さめにマイグレートしていって課題にぶつからないか検証していけると良さそう

このスクラップは2023/03/30にクローズされました