クックパッドのNext.jsとGraphQLへのリプレース
今日のタブ記事はこちら。
レシピサービスのフロントエンドを Next.js と GraphQL のシステムに置き換えている話
クックパッドはRailsで有名な会社なので、Next.jsとGarphQLに置き換えるのすごいな〜と思ったら、CoffeeScriptやjQueryが現役で稼働している状態からの置き換えらしい。すごすぎる……
さすがにクックパッドくらいの大きなプロダクトだと一度に全ては無理だよなと思ったらちょっとずつ変えているとのこと。というかクックパッドレベルだとどんな技術の入れ替えや追加や削除でもちょっとずつになりそうだなあ。
- まずTypeScriptを中心に据えることを決め
- 次にTypeScriptと相性がいいReactを使うことを決め
- サイトの性質上SSRが必要なのでNext.jsを使うことを決める
という技術選定らしい。合理的だと思う。
そしてNext.jsからAPIを直接呼ぼうと思ったが認証やReq/Resの型付けなどの理由でBFFサーバーを挟みそこでGraphQLを利用。自分はGraphQLを利用したことがないので想像でしかないんだけど、Next.jsとBFFサーバーのやり取りはGraphQLで、BFFサーバーとAPIサーバーのやり取りはRESTでということなのだろうか。
BFFサーバーにGraphQLを利用する利点として、graphql-codegenというライブラリを利用することでReq/Resの型定義ファイルを自動生成できるらしい。今は大量のReq/Resの型定義があるプロダクトを扱っているので、型定義を自動生成できるのはすごいな〜と思う。
そしてこういうだんだんリプレースしていく系の記事でいつも「特定のページだけ新しいほうを返すのってどうやるの?」ということを疑問に思うのだけど、記事によるとリバースプロキシ(Nginx)で制御しているらしい。
スマートフォンからの/recipe/:id
をNext.jsへ、/graphql
をGraphQLのBFFサーバーへルーティングして、それ以外をもともとのモノリシックなRailsのほうへルーティングしているということらしい。なるほど、こうやるのか。
パフォーマンスもかなり改善しているらしく、FCP(First Contentful Paint)がもともと遅かったのがよくなったとのこと。
改善できた理由として、巨大なCSSやdeferできないJSがheadで読まれていたせいでクリティカルレンダリングパスが最適化できていないことらしい。
分からない言葉がたくさん出てきたのでまとめて調べよう。
FCP
FCPを全然知らなかったのでググったのだが、
First Contentful Paint (FCP) は、ブラウザーが DOM からコンテンツの最初のビットをレンダリングし、ページが実際読み込み中というユーザーへの最初のフィードバックがなされる時間です。(引用)
上記はMDNの引用。
リクエストが飛んでからロード中のインジケータが出るまでみたいなことで合ってるのかな?
defer
defer自体は「延期する」みたいな意味らしい。
わかりすい説明があった。
scriptタグの属性にasync/deferというのがあって、
- 何もつけないとHTMLのパース中にscriptファイルのダウンロードと実行の処理が割り込みで入ってきて、それが終わるまではHTMLのパースが中断される同期的な処理
- asyncをつけると、HTMLのパースとscriptファイルのダウンロードを非同期で行い、scriptファイルのダウンロードが終わったらHTMLのパースを中断して同期的にscriptを実行し、それが終わったら中断していたHTMLのパースを再開する
- deferをつけると、HTMLとscriptファイルのダウンロードを非同期で行うまではasyncと一緒で、scriptファイルのダウンロードが終わってもHTMLのパースが終わるまで実行せず、パースが終わったらダウンロードしたscriptファイルを実行する
ということらしい。
deferを使うことでscriptタグが書かれた順にちゃんと実行されるのが保証される上、HTMLのパースが終わってDOMの構築が完了しているのでDOM操作が確実にできる、とのこと。
クリティカルレンダリングパス
クリティカルレンダリングパスの最適化を参考にしています。
クリティカルレンダリングパスとは「ユーザーの操作に関連するコンテンツの順位付け」という意味らしい。これだけ聞いてもピンとはこない。
これを最適化するには、CSSの最適化とJSの最適化をするといいらしい。
最適化するというのは、ファーストビューのコンテンツに必要なCSS/JSだけをインライン化し、残りのCSS/JSはページの下のほうで読み込ませるようにする、ということらしい。
まずもって、ブラウザはHTMLの解析中に外部スクリプトに遭遇するとその実行が終わるまで解析を中断したり、外部CSSのダウンロードと処理が完了するまでコンテンツの描画をブロックしたりするらしい。それを避けるためにはインライン化が必要だけど、全てをインライン化するわけにはいかないので、ファーストビューの表示に関連するものだけをインライン化するのがよいとのこと。なるほどな〜。
もどる
これらを学んだ上で戻ると、「巨大なCSSやdeferできないJSがheadで読まれている」というのはかなりキツいことが分かった。
deferできないということはつまり全て同期的にしか処理が進められないということなのでかなり渋そう。
deferできない原因は、hamlに書いてあるJSがheadで読まれるJSに依存しているかららしい。RailsのView Templateを使うとそういう問題もあるんだな……。
あとNext.jsにはWeb Vitalsの計測機能があるそうなので、今度使ってみたい。
JSONのDynamic Keyについて
昨日の投稿でJSONどっちがいいかみたいな話が盛り上がってたの見て書かれた記事だと思う。
ツイートをもう一度貼っておくと
JSON詳しい方にお聞きしたいのですが、Aみたいなデータを返すREST APIの流派があるんでしょうか??
— Tetsu (@wtetsu) June 7, 2021
サーバサイドでもフロントエンドでも、なんかAPI呼んでAが返ってきたら使いづらくて仕方ない気がするのですが…
※私はBにすべきだと思うが「Aがモダンでスタンダード」的な主張とぶつかっている pic.twitter.com/O9NGvmXyRu
これのAは、そもそもJSONの定義としては不適切らしい。あとArrayの中は型を定義できるがMapの中だとできないらしい。そうなのか。
Aも使われるシーンはあるらしく、一概にAのほうが悪いとは言えないらしい。なるほど。
トレイリングスラッシュについて
「このままだと攻撃者の思う壺」イオンカードで不正利用の注意喚起のはずが…大間違いだった
togetterでイオンカードの不正利用についての注意喚起がよくない書き方してるよ、と話題になっていた。
https://......jp/ とするのを後ろの /が抜けて一大事ってとこですかね。 (引用)
こういうリプライを見つけたんだけど、つまりトレイリングスラッシュが抜けていてよくない、ってこと?だよね?
トレイリングスラッシュってめちゃ大事じゃん、知らなかった……と思ってググってみた。
- トレイリングスラッシュがない場合はファイルへのリクエスト
- トレイリングスラッシュがある場合にはディレクトリへのリクエスト
で、
- ディレクトリにリクエストするとそのディレクトリが返すべきファイル(だいたいindex.htmlとか)が返される設定をしてあることがほとんど
さらに
- トレイリングスラッシュなしでファイルにリクエストし、そのファイルがない場合404になるが、設定してあるとトレイリングスラッシュをつけた状態でもう一度探してくれる
らしい。
色々ググってみたんだけど、トレイリングスラッシュなしがセキュリティ的にまずいというのが分からなかった。誰か詳しい人教えてほしい……。