最近、Next.jsが複雑になりすぎて、単なるウェブ画面を作る用途にはNext.jsは重すぎるので別のものが良いとか、Vercel統合のための機能が多いんでしょ、みたいな感想を見かけることが増えた気がします。特に管理画面とか社内システムとかですね。B2Cでも設定画面系とかは当てはまるかもしれません。
ホンダ時代に、タイプRを買っても実際にサーキットとかに走らせに行く人は1/10ぐらい、という話を聞いた気がしますが、必ずしも、そのすべてのパフォーマンスを引き出さないのはダメとかなくて、単にかっこいいからとか、一部のメリットでも自分にあえば良いのです。
Next.jsも、たくさん機能がありますが、ミニマムな使い方もできます。
ほぼNext.jsの機能をオフにした使い方
たぶんNext.jsを最低限で使うライン・メリットはここかな、と思います。
- 基本的に全部CSR(Client Side Rendering)で普通のReactっぽく使う
- Routerがあらかじめ組み込まれている
- 超高速なビルドツール群が組み込み済み
- 広く使われるライブラリ群との組み合わせがテスト済み
そのためにやるのは
- API機能を使わずに別に実装済みのAPIサーバーにフロントエンドからリクエストを投げられるようにする
- 静的HTMLとしてビルドできるようにする
の2つです。
プロジェクト作成
$ npx create-next-app |
今時はTypeScripは使うだろうし、ESLintも使うだろうから、Enter連打しておけば大丈夫です。ぼくはTailwind.CSSも使うのでこれが最初から選べるのはうれしいですね。
リバースプロキシ設定
API機能を使わないということは別にGoなりJavaなりでAPIサーバーがいるはずですので、そちらにフォワードするように設定を書き換えます。
/** @type {import('next').NextConfig} */ |
別に立ってるAPIサーバーのエミュレーションのために、以下のようなファイルを作ってからプロジェクトのルートで python3 -m "http.server"
と実行して8000番でウェブサーバーを立ててみます。
{ |
これで、Next.jsの3000番ポートの特定のリクエストはフォワードされるようになります。/api
へのリクエストはバックエンドサーバーに直接行くので、API機能は触る必要はありません。もちろん、最初から別のポートにフロントからリクエストを飛ばす方法もありますが、こちらの方が同一ドメインなので、CORSも不要です。また実際にデプロイするときも同一ドメインであれば追加の設定とかも不要で楽ですよね。
こんな感じの動きになります。
データ取得の実装
データ取得はgetInitialProps()とか、getServerSideProps()とか、getStaticProps()を使わずに、コンポーネントの中から行います。
useEffect()
を使って、その中でfetch
を使ったり、useSWR()を使ったり。まあ、データ取得のこの方法は新しいapp routerでは標準なので、新規システムの場合は最初からこちらで行っていけば良いです。
静的生成
最後に静的生成です。昔はnext buildを実行してからnext exportをすれば生成されていましたが、今はoutput: 'export'
を足すという感じで変わっています。ただ、これだとexportで使えないはずの機能をチェックするエラーチェックでひっかかってしまいます。というのも、rewritesはサーバーがある前提の機能だからです。
const nextConfig = { |
ちょっとトリッキーな方法ですが、ビルド時はrewrite()
をなくしてexport
にするような設定にすれば両立できます。
module.exports = process.env.npm_lifecycle_event === 'build' ? { |
本番ではおそらくLBなどが前段にたっていて、特定のパス(/api
など)以下はAPIサーバーにフォワードし、そうでない場合はオブジェクトストレージにフォワード。ただし、404ならindex.html
にフォールバック、みたいな感じにすればNode.jsのサーバーなしにNext.jsアプリケーションがデプロイできます。
これでミニマムなNext.jsができました。
追加の機能のオプトイン
Next.jsのいくつかの機能は、ニーズに当てはまるのであれば有効化していく、という感じでオプトインしていくとよいでしょう。
静的生成(SSG/ISG)
ISG(Incremental Static Generation)とかSSG(Static Site Generation)がうれしいのは読み手が多くて書き手が少ないコンテンツ、例えばニュースサイトとかブログとかですね。キャッシュを使う仕組みの場合、どうしても性能を上げるにはキャッシュヒット率が大事になりますが、キャッシュで済ましてはいけない新しいコンテンツでもキャッシュが使われると更新されない内容が表示されますし、Revalidate戦略を使うと、結局何度もコンテンツ生成ロジックが実行されることになります。閲覧数と更新頻度の比率がどれだけ偏りがあるかで判断すれば良いと思います。
サーバー側の生成(SSR)
画面表示にたくさんのデータ取得が必要だけど、集計したり、一部のカラムだけ抜き出すとかaggregationしちゃうから、実際に画面から使われるデータ自体は多くないよ、みたいなケースではSSR(Server Side Rendering)がうれしいですね。もちろん、SSRだけが回答ではなく、データのフィルタリングがきちんとAPI側に組み込まれていたら、実際は不要にできるかもしれません。
あと、画面表示には大量のJavaScriptのロジックが必要だけど、実際に表示はそこまで複雑ではない、みたいなケースだと、React Server Component機能が効くと思います。ぼくもまだ仕事では使ったことがないですが・・・まあこれがうまく性能を出してくれるケースがどれだけあるのか、というのはあるかと思います。社内システムなんかの場合は通信速度が問題になることは少ないと思うので、JSのファイルのダウンロードを無理に早くする必要もないと思いますし。
あとは、社内システムでもスクレイピングしたりRPAしたい、というケースだともしかしたらSSRをしておいた方が良いというのもあるかもしれませんが、基本的にネガティブな動機だと思うので、そういう目的の場合はAPIでアクセスして! って言う方が良いと思います。
NoCodeとか、RPAって「エンジニアという気持ち悪い生き物と話をしたくない」という需要がそれなりにある気がしているんだよね
— ところてん (@tokoroten) July 30, 2020
API機能
既存のサービスへリクエストを投げてそれをただ返すだけであれば魅力はないと思います。
- クレデンシャルをフロントエンドに露出させないでサーバー側の中だけで持つ
- バックエンドサーバーのレスポンスの形式がフロントエンドに嬉しくないので整形したい
- バックエンドサーバーが複数あるので、BaaSというか、BFF(backend for frontend)として動かしたい
今まではデータ通信をgetInitialProps()
でまとめることで、ページ表示のタイミングにまとめて行うことで、画面が白くなる時間を減らす、という効果もありましたが、ap routerが入って、別々のコンポーネントから投げられる重複した余計な通信はまとめられる、みたいな最適化が13で入ったので、パフォーマンス目的で入れる必要性はなくなったと思います。
もちろん、DBアクセスとかをNode.jsを使って、API機能を使ってやりきるのであれば、Node.jsのサーバーをデプロイする必要がありますがAPI機能を使うのが一番簡単でしょう。
それでもNext.js以外を選択するケース
ぼくがNext.jsをあえて使わずにコードを書くのは、Routerすらないようなシンプルなサンプルプログラム程度の場合が多いですね。その場合も、create-next-appではなく、vite.jsのReactテンプレートで作ります。TypeScriptも最初から入って、ビルドもそこそこ高速でホットリロードもできて、十分です。
ただ、仕事で作るシステムであれば、どんなに小さくてもRouterも必要になるでしょうし、swcとかTurbopackとかどんどん新しいものが導入されてるNext.jsの方が良いかな、と思います。
あとは、ビルドサイズが小さいというところでSolid.jsとかSvelteとかは触ったりもしますが、基本的に大きなものを作るときはNext.jsでいいかな、と思っています(Vue.jsを選ぶ時以外)。
まとめ
Next.jsはeasyであるがsimpleではないというのを地でいく発展をしています。内部ではかなり複雑なことをやってくれています。とはいえ、裏の仕組みが表に出てこないようにかなり気を遣って開発されているため、ドキュメントに書かれている表の機能だけを見ておけば良い安心感があります。正規表現を使うのに、正規表現エンジンのコードを読んだりする人は少ない(ゼロとは言わないですが)のと同じように、Next.jsの中の実装を確認しにいく必要性というのは業務で使っていてもあまり感じないです。GoとかTypeScriptの処理系の方が読んだぐらい。なので、Next.jsの表だけ見て簡単な機能だけを使うのはありだと思います。そのためにも、Next.jsを使うにあたっては、自分のユースケースにマッチした機能の選択が大事になります。1つの機能を実現するための手段がいくつかありますからね。ミニマムなNext.jsの機能から始めて、必要になったら徐々にNext.jsの機能を有効化していく、というのが一番良いなと感じています。
機能追加も活発にされているように見えるのですが、ずっと追いかけてきた立場から見ると、「よりeasyであろう」としているように思えます。
Next.js、いろいろ複雑に見えているけど、今ベータの機能が安定化するとかなり簡単になると思うのだよな。どこのコンポーネントからも自由にサーバーアクセス。裏で勝手にまとめて最適化するよ、とか。フォルダ構成もページ関連のコンポーネントを集めておけるようになる。
— 渋川よしき (@shibu_jp) May 1, 2023
app routerになると、PureなReactにはない、Next.js固有のルールというのは減ります。Reactを学んだ人が、追加で学ぶことも減ります。
ちょっと前にclassmethodさんのNext.jsのブログエントリーも出てましたが、app routerでいらなくなる部分とかを省いたり、最初から設定されているTailwind CSSを前提にしていけばさらに半分ぐらいになるんじゃないかと思います。なので、怖がらなくてもいいし、複雑だからと避ける必要もないな、と思っています。
Next.jsの新機能がぱっと見理解できなくても、自分が考えてなかった新しい発想を元にしているかもだし「Next.jsは難しすぎるオワコン」と投げ捨てずについて行くことで見える世界があるだろうし、投資というのは常に先を見ているはずなので、みんな脊髄反射せずに実写の聖闘士星矢見に行こうぜ。
— 渋川よしき (@shibu_jp) May 8, 2023