nabeliwonote

next.js は Real World で使えるのか

最近はいろんなところで React アプリケーション(もちろん React とは限らないが)での SSR について語られることが多くなっていると感じる。
僕も個人の趣味プロダクトで Express サーバーで React コンポーネントをレンダリングするみたいなのをやったことがある。

で、その感想としてはとにかくめんどくさい。
SSR したい目的って SEO だったり OGP だったりみたいなクローラーに対しての表示か、もしくは初期表示の高速化の2つくらいだと思うんだけど、その目的に対する解答として SSR の自前実装はコスパ良いのかって考えるとなんとも微妙なところ。
特にネックなのが react-router で、バージョンが上がるたびに破壊的な変更が入ってアプリケーションが壊れるわけだけれど、その対応がクライアントのルーティングだけじゃなくて SSR の部分でもやんなきゃいけないのがとにかくめんどくさそう。

そして最近は code splitting だとかパフォーマンス改善のためのやることもいっぱい、とか考えだすともうアプリケーションのロジックに割く時間は失われていく一方だ。

そこで next.js である。
今 next.js が注目されているのはそういうめんどくさいあれこれを全部一挙に引き受けてもらって、僕らがアプリケーションロジックを書くことだけに集中できるのでは、という希望が見えるからだ。

そもそも next.js ってなんだっけ

next.js は Universal な React アプリケーションを作るためのフレームワークだ。
React SPA で SSR するための難しいところを全て next.js が吸収してくれる。

ここで全ての機能を説明する気はないので詳細は公式のドキュメントを見てもらうとして、大きな特徴だけまとめておく。

  • ファイルシステムがルーティングになる
  • サーバー側でのデータ取得用のメソッドが使える
  • ビルド用のめんどくさい設定が必要なし

ファイルシステムがルーティングになる

アプリケーションルートに pages ディレクトリを作り、そこに **.js を配置することでそれが /** という URL にマッピングされる。
**.js では React コンポーネントを export default することでページがレンダリングされる。

もちろん head タグの中身とかもページ毎に自由に変えられる。

サーバー側でのデータ取得用のメソッドが使える

SSR する場合、クライアントで描画した後ではなく、ブラウザに DOM を返す前にデータを取得して、そのデータを埋め込んだりデータの中身によってはリダイレクトをしたかったりする。
そのために getInitialProps というメソッドを使うことができる。
React.Component を継承したコンポーネントで static getInitialProps() {} という形でメソッドを生やすと、対応する URL に来たときにそのメソッドがサーバー側で呼ばれて事前に実行される。

だから async くっつけちゃえば await でデータフェッチを待ってからレンダリングができる。
さらにそのメソッドでデータを return すればそのコンポーネントの props としてデータを受け取ることができる。

ビルド用のめんどくさい設定が必要なし

next.js は内部で webpack を使っていて、今まで僕らがやっていたようなビルド周りの設定も全部引き受けてくれている。
さらに code splitting もページ単位ではあるが勝手にやってくれる。

試しに next.js を入れてアプリケーションを動かしてみるとわかるが本当に簡単に SSR できる SPA の土台が用意できる。本当に今までの苦労はなんだったのかという感じ。

銀の弾丸なのでは?

ここまでの話を見たら当然使わない手はないのではってなるだろう。
つまり next.js とは SPA で SSR する際の銀の弾丸なのでは?という話。

そんなこと言いつつ銀の弾丸なんて幻想なので Real World では何かつらみがあるはずだと穿った見方をして実際に手元で todo アプリよりはまともなサンプルを作ってみたりした。

問題になりそうだなと思っていたのは以下の点。

  • サーバー側の細かい設定が必要になったとき対応できるのか
  • DB を持つ必要があるサービスの場合どうするか
  • パフォーマンスは本当に大丈夫なのか

実際に作ってみて自分の中で答えが出た。

サーバー側の細かい設定が必要になったとき対応できるのか

これはなんら問題がない。
というのも、ルーティングやサーバー側の処理はこちらで自由にカスタマイズするためのやり方が用意されている
しかしカスタマイズをすると別の問題が発生する。

本来 next.js のサーバーを起動する場合はアプリケーションルートで next というコマンドを叩くだけで ok だ。
カスタマイズする場合は、 server.js を用意してそこに next.js の設定をゴニョゴニョする必要がある。そしてサーバーを起動する場合は node server.js というコマンドを叩くことになる。

そうなると next.js がサーバーを起動する際にやってくれてた設定をこっちで書かなきゃいけなくなってしまう。
例えばファイル変更時のライブリロードだったり、 import/export の構文への対応だったりとかだ。

設定が必要ないというメリットが若干失われてしまう。
外部 API を叩いて取得したデータを使うだけのサービスであればサーバーの設定をどうこうする必要はないが、自前でデータを持つ必要がある場合はそれなりにサーバーサイドのコードを書く必要があるのでそこらへんが分かれ目になる。

この設定を自分で書くか next.js に乗るかはアプリケーションロジックを書き始めるまでのスピードが全然変わってくる。
ただ next.js を使わない場合は毎回やっていることをやるだけなのでメリットが薄れるわけでデメリットになるわけではない。

DB を持つ必要があるサービスの場合どうするか

上にも書いたが外部 API を叩くだけであればクライアントのコードだけで完結するので気持ち良い書き味のまま作りきれる。
ただ実際のプロダクトで自前のデータを持たないパターンの方が珍しいと思われるのでここをどうするのか考えなければならない。

じゃあ自前で DB を用意して API を用意してって場合は当然 express を用意する必要がある。
express に next を繋げて共存させる形になる。そうなると結局 API 用のルーティングを用意することになる。そして気づく。これ別に next.js いらないのでは?と。

API 用のルーティングを express で用意しちゃうともはやビュー用のルーティングも express で設定しても良いのでは?となったり。
上で述べた通りこうなるともはやサーバーを起動する際の設定を書かなきゃいけないという残念さもある。

ただそれでも getInitialProps は有用なので next.js 使わないという選択にはならない。

パフォーマンスは本当に大丈夫なのか

next.js でパフォーマンスに関わる部分といえば code splitting がある。
デフォルトでページごとにファイルを分けて別ページのファイルは遅延読み込みしてくれる。さらに next/link を使うと react-router で言うところの Link を設置できるのだが、ページ遷移時にデータの prefetch をすることができる。
ページ遷移する前にデータの準備ができるので高速で次ページのデータを表示できるというわけ。

これを勝手にやってくれるのは本当にすごい。
ただ昨今の code splitting 事情はページ単位だけでなく、コンポーネント単位での splitting が求められるようになっている。
ウィンドウのスクロールに応じてコンポーネントのファイルを遅延読み込みすることで初期読み込み時のファイルサイズを極限まで小さくして高速化をはかっているプロジェクトの話もちらほら聞く。

そこまでやろうと思うと next.js では足りなくて自前で webpack ごにょごにょしたり遅延読み込み用の記述ごにょごにょしたりとかが必要になってくる。
本当にそこまでやる必要あるのか?という話もある。そこのパフォーマンスよりも考えるべきボトルネックがある場合も多い。ただそれでもなおそこまでやる必要性がある場合は next.js を選ばないというパターンもあり得る。
っていうのは特例だと思うので基本的にパフォーマンス面で問題になることはないはず。

ホスティングどうするか問題

上記の通り、もちろん next.js について初めて知ったときに描いたような理想な開発が完璧にできるというわけではないが、それでもこれを使うことで省くことができるつらみは多い。
なので僕としてはガンガン next.js を押していこうと思っている。

では next.js でアプリケーションを作るとして、どこにホスティングするかという問題がある。
この項目に関しては実際に next.js を使ってプロダクトをリリースして運用した経験がないのでロマンの話しかできない。
とりあえず一つ言えることとしてはここまでクライアント完結のコードを目指したのだからサーバーレスを選択したい。

next.js を作っている ZEITnow というツールを提供している。
試しに使っている話はよく見るけれど実際のプロダクトで動かしている例は見たことがない。ただ公式のドキュメントを読む限りは月$14.99でまともに使えるっぽいので選択肢としては良さそう。
データベースも用意してくれるっぽい

有名どころとしては Google か aws のサーバーレス技術がある。
基本的にはこの2つはどちらの技術セットを選択してもほぼ同じことができる。なので今回は Google のサーバーレス技術での話をする。

Next.js を Firebase hosting で動かしてSSRする
この記事に夢を見た。

Firebase Hosting + Firebase Functions + Firebase Authentication + Cloud Firestore + next.js という組み合わせによって夢は実現できる。
ネックなのが Firestore で、これは kvs なので RDBMS に慣れている我々にはデータ設計がだいぶ難しい。

今この組み合わせでログイン認証付きの todo アプリをサンプルとして作ってみているが、なかなか満足いく形にならずにやきもきしている。
ここらへんは調査・慣れの問題でもありそうなので少しずつ進めていきたい。また進捗あったら実際のコードレベルで知見を共有できればと思っている。終わり。