Next.jsのライフサイクルと外部APIのタイミング


簡単にReactを使ったSSRができるNext.jsを使っていて外部APIをfetchするときに色々戸惑ったので覚書き。

公式ドキュメントが結構偏っているのと日本語情報も少ないのでわりと手探りでどうすればいいか考えていきます。

next 9.1.7 react 16.12.0

データフェッチ

ドキュメントではdata fetchingとしてgetInitialProps()を使う方法があります。

これをこのまま他の外部APIに変更するとすぐにCORS policy: No 'Access-Control-Allow-Origin' headerと怒られることになります。

CORS(Cross-Origin Resource Sharing)許可をしていない大抵のAPIはサーバーサイドであれば大丈夫ですが、クライアント(ブラウザ)で実行したときに上記エラーになります。

つまりこの読み込みでAPIを叩くときはサーバーサイド実行を意識したり、クライアントで動く可能性があるときはCORS許可されたもの、あるいは自分自身のAPIを使う。

サーバーかクライアントか判断する

getInitialProps(ctx)がサーバーサイドかクライアントサイドか判断するには引数オブジェクトの{ req, res }が存在するかどうかを見ます。

カスタムページでは_app.jsは引数オブジェクトが一つ上でコンテクストがctxにあるのでobj.ctx.reqで判断します。

また_document.jsはそもそもクライアントで実行されません。

Nextライフサイクルとコンテクストオブジェクト

上の例であるようにドキュメントのcontext objectはページのものです。

ライフサイクルと共に画像にしてみます。

まずは初回アクセス時の動作です。

getInitialProps()から完了してからReactのライフサイクルです。

また内部では_app.js → page.js → _document.jsの順に実行されます。

また最初のアクセスでgetInitialProps()はサーバーサイドでのみ実行されます。

 

次に内部で遷移するときの動きを見てみます。

サーバー側での処理はありません。なので_documentも動きません。

パラメータとしてはreq, resがないので処理の分岐ができるわけです。

getInitialProps()がクライアントで走るんですが、ここで外部APIを叩いていると最初に書いたエラーになります。

 

ところでカスタムページのgetInitialPropsは途中でawait処理が入ります。

これをふまえてどういった順番で動くか確認してみます。

ついでにレンダー処理もログに出すようにします。

クライアント側はこれの_documentをなくしたものとなります。

_appawait後にpageのコンテクストを使えるが_documentは他処理後に始まるようで思ってたより変な動きをしています。_documentはSSRされたものを注入するものだと考えればこの順番も納得できます。

それでどうするか

まず内部遷移をなくすという方法が考えられます。

コード的には<Link><a>に変える感じになりますが、Reactの利点を消しつつ多くのデメリットを抱えることになり全体的にもっさりした動作になると思います。

 

遷移に依存しないところでパラメータを管理してもいいです。

最初のサーバーアクセスで取得した情報を使いまわす感じです。

_documentで管理するなどの方法もできなくはないですが、_appでサーバー判定しつつpropsに注入してconstructorグローバル管理すればそれっぽく動きます。

router.pushを使用して遷移時にパラメータを渡すというアクロバティックな方法も考えられます。

 

これらはサーバーとクライアントを意図的に分けて対策する方法なので注意書きにあるように適切な扱いをしないとサーバー用モジュールをクライアントに読み込ませてしまったりして速度の低下をもたらします。

If you are using server-side only modules inside getinitialProps, make sure to import them properly, otherwise it’ll slow down your app

 

別方向の対策としてCORSが問題になるなら自前でラップしたAPIを用意する方法があります。Next.jsなら数行。

パラメータもダイナミックルーティングもreq.queryに格納されるのでそのまま外部APIに流して結果を返すだけです。

同じNetx上に乗ってはいますがこっちの方が疎な関係です。

将来的にAPIを切り分けてnext exportで静的ファイルにしたりもできるかな。

この場合は無駄な通信が発生しないようにキャッシュ利用なんかが重要になりそう。

 

どの方法にしてもgetInitialProps()で取得する以上、アクセスに時間がかかればその分表示が遅れることになるのでタイムアウトも気にしておきたい。

追記:サーバーかクライアントかの判定

processの違いを見てて気づきましたがprocess.browserで判定できました。

ブラウザのグローバルオブジェクトwindowの有無で確かめる方法もあります。同様にブラウザ特有のdocumentでも判定できます。

逆にglobalの有無でも行けるかと思ったけどブラウザでも定義されていたため判定には使えませんでした。

ドキュメントにはないですがサンプルでは使っているのもありましたし、こっちの方が使い勝手がよさそう。


コメントを残す

メールアドレスが公開されることはありません。