Next.js × Relay
モチベーション
Next.jsはv12.1でGraphQLクライアントであるRelayのコンパイルをswcサポートしたが、コンパイル部分のサポートのため、実はRelayをNext.jsに組み込もうとすると色々とハードルがある
ここでは、いくつかのソリューションを試してみてそれぞれのメリットデメリットが説明できる程度にまとめられるといいな
いくつかソリューション
- https://github.com/dgca/next-relay-bridge
- https://github.com/RevereCRE/relay-nextjs
- https://github.com/vercel/next.js/tree/canary/examples/with-relay-modern
- https://github.com/relayjs/relay-examples/tree/main/data-driven-dependencies
議論されているissue
with-relay-modern
Next.js公式のサンプル
公式サンプルなので、色々と網羅していてほしいものだがいくつか微妙そうな部分があるので確認していく
良さそうな部分
- きちんとSSRにも対応できそう(サンプルはgetStaticPropsだが、見る感じgetServerSidePropsでもいけそう
- そもそもRelayはサーバーサイドフェッチをする公式サンプルがないため、Next.jsと組み合わせるのには良いサンプル
微妙そうな部分
- jsで書かれている
- usePreloadedQueryが使われていない
- propsでページに渡している
- サーバーサイドでフェッチはできているが、これは型解決ができないのでは?
- relayはusePreloadedQueryをしたときに、キャッシュからデータを復元するが、それに対し型付けをしている気がするので
- クライアントフェッチ部分が書かれていない
- サーバーサイドでのみフェッチする仕組みになっているが、ポーリングしたい部分等、一部クライアントフェッチしたい部分というのは出てくる
- おそらくそちらは公式のusePreloadedQueryを使ってしまえばいけるんだと思うが、あまりにもサンプルが簡素なのでその辺りは自分で模索する必要あり
- サーバーサイドでのみフェッチする仕組みになっているが、ポーリングしたい部分等、一部クライアントフェッチしたい部分というのは出てくる
- Suspenseが用いられていない
- relayではuseLazyLoadQueryが使用されるコンポーネントをSuspenseで囲むことで遅延読み込みを可能にしているが、こちらのサポートがされていなさそうに見える
- 他にもrelayはfragmentで
@defer
を用いた場合、データの遅延ロード&Suspenseによる遅延表示を実現しているがこちらもサポートしていなさそう- そもそも@deferにサーバーサイドが対応していなければ関係のない話なのでそこまで大きな話ではないのかもしれないし、サーバーサイドでpreloadした場合、@deferしたデータがどこに届くのかよくわかっていないので、これに関してはそこまで問題ではなさそうな気がする、、(わからない
- バーション追従などは適宜行われているものの、基本コンセプトは3年前からほぼ変わっていない
tsに書き換えようと思ってやってみたけど、結構しんどい、、
relay-compilerのバージョンも古くて、tsに書き換えたときにcompileがうまく通らない。。(フラグメント周りがうまく処理できない。。
一旦、良さそうなソリューションでもないので、また時間があったら検証
relay-nextjs
おそらく一番進んだソリューション
良さそうな部分
- usePreloadedQueryやuseLazyLoadQuery等、標準のAPIを使用可能
- サーバーサイドのプリロード、クライアントサイドでのフェッチもサポートしている
- useLazyLoadQueryのSuspense対応もできている
- おそらくtsサポートもされていそう
微妙そうな部分
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.
relay-runtimeのバージョン的な問題な気がするけど、、(なんかあまりメンテされてない感じがしてどうもやる気が起きない。。
relay-nextjsをv1.0.0にダウングレードをしたら動いた(latestはv1.0.2)
とりあえず全然わからんかったのでissueを立てておく
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のデータ(=クリティカルなリクエスト)を用いて何かすることができない
という意味かな?
まぁそんなことする必要ないでしょ?(そのままコンポーネントに渡されて、使うだけなんだから)
というのはわかるけど、サーバーサイドでそのままデータ直接参照したい場合ってないわけではないからそういうことかな?
SG(SSG)のサポートがない
これは確かになさそう(オプションが見当たらない)
並行レンダリングのサポートがない
これに関しては知識不足で検証方法がわからないが、まぁ高度な話だと思うので、特段これ以上調べない。
ちなみに、useLazyLoadQueryに関してはサポートされてるっぽいので、クライアントフェッチはできるはず
評価: △
完成度は高いが、relay-nextjsへの依存度が高めのコードが出来上がってしまう印象
- TypeScirptがちゃんとサポートされている
- usePreloadedQuery等のデフォルトが使える
というのはGood
next-relay-bridge
開発がストップしてそうなのでスキップ
data-driven-dependencies
Relayの中の人が書いてくれたサンプル
上記はFlowで書かれているが、TypeScriptで書き直してくれた人がいるので以下のURLをベースに
良さそうな部分
- TypeScriptサポート
- getServerSidePropsを拡張することなく作れる
- 並行レンダリングのサポートがあるっぽい
- SGだろうがSSRだろうが使えそう
- usePreloadedQueryを使ったサンプル
- useLazyLoadQueryに関してはサンプルがないが、Suspense対応しているためできる気がする
微妙そうな部分
- サーバーサイドでプリロードしたデータをクライアントサイドキャッシュに詰め込むときにかなり無理をしている
- ライブラリ群(ハイドレート部分等)が自分達で実装になるため、基本コピペで良いだろうが、メンテナンスを自分達でする必要あり
- クライアントサイドフェッチのサンプルがなさそう
- クライアントサイドも考慮されてそうだけど、サンプルがなさそう
無理している部分について
セットしている部分
取り出し部分
実際にenvironment.getNetwork()
をconsole.logしてみると以下のようなものが出てくる
{responseCache: RelayQueryResponseCache, execute: ƒ}
responseCacheオブジェクトが入っているが、privateなapiっぽい(多分、クライアントサイドで扱う分には、usePreloadedQueryとかのAPIが内部的にアクセスするだけなので、外に出す必要がないということだと思う
クライアントサイドフェッチのサンプルがなさそう(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;
ライブラリ群(ハイドレート部分等)が自分達で実装になるため、基本コピペで良いだろうが、メンテナンスを自分達でする必要ありという部分
色々見ていくと、無理やりカスタマイズしているところはあるものの、全体的に見ると割とシンプルになっているし、メンテナンスが多く発生するところでもないので良さそう
評価: ○
思っていたより完成度が高く、辛いポイントがlibs配下にしまえそうなので、良さそう
- TypeScirptがちゃんとサポートされている
- usePreloadedQuery等のデフォルトが使える
- 最低限の_app.tsxとかの拡張に止まるので、負債を中にしまい込んでしまえば標準機能で綺麗に作っていけそう
- getServerSideProps等の拡張も必要ない
responseCache部分について
無理やりなんとかしてる部分について、よりうまくできないか考えてみる
何をしているのか?
1. サーバーでプリロードする
2. Relayコンテナに1を渡す
3 environmentを作成する
3.1 relayのenvironmentを作成する
3.2 3.1と同時にnetworkというものを同時に作成する
3.3 3.2と同時にデータを格納しておくcacheの箱を作成
3.4 3.3で生成した箱を3.1で作ったnetworkに無理やり格納しておく
3.3で用意した箱はあくまでも、classを呼び出しているだけなので、networkに対して無理やり箱をつくりあげている
3.5 最終的にはnetworkも3.1で作ったenvironmentに格納してあげる
以上で、キャッシュを入れておく箱の準備完了
4. データを整形
5. 3で作ったenvironment>network>responseCacheに対して、4を突っ込む
6. 最終的にコンポーネント側が知りたいのはデータではなく、データのありか(ref)なので、それをpropsとして整形して返してあげる
クライアントサイドフェッチのサンプルがなさそう(useLazyLoadQuery)に関しての続き
useLazyLoadQuery
を実行した時、Suspenseのfallbackが実行されるだろう。と思い、実行
結果、SSRではFallbackが実行され、クライアントサイドでは実行されず。
SSR側でSuspenseを待ち合わせ、出来上がったDOMを渡す?
でも、クライアントサイドでもデータのフェッチは行ってそう
よくわからない。(Suspenseの知識が足りていないのか
ということで、現時点ではdata-driven-dependencies
による解決策が一番良さそう
なんかまだ見えていない課題がありそうな気がするけど、、
小さめにマイグレートしていって課題にぶつからないか検証していけると良さそう