フューチャー技術ブログ

管理画面等でNext.jsをBetter Reactとして使う

最近、Next.jsが複雑になりすぎて、単なるウェブ画面を作る用途にはNext.jsは重すぎるので別のものが良いとか、Vercel統合のための機能が多いんでしょ、みたいな感想を見かけることが増えた気がします。特に管理画面とか社内システムとかですね。B2Cでも設定画面系とかは当てはまるかもしれません。

ホンダ時代に、タイプRを買っても実際にサーキットとかに走らせに行く人は1/10ぐらい、という話を聞いた気がしますが、必ずしも、そのすべてのパフォーマンスを引き出さないのはダメとかなくて、単にかっこいいからとか、一部のメリットでも自分にあえば良いのです。

Next.jsも、たくさん機能がありますが、ミニマムな使い方もできます。

ほぼNext.jsの機能をオフにした使い方

たぶんNext.jsを最低限で使うライン・メリットはここかな、と思います。

そのためにやるのは

  • API機能を使わずに別に実装済みのAPIサーバーにフロントエンドからリクエストを投げられるようにする
  • 静的HTMLとしてビルドできるようにする

の2つです。

プロジェクト作成

$ npx create-next-app
✔ What is your project named? … simple-next
✔ Would you like to use TypeScript with this project? … No / [Yes]
✔ Would you like to use ESLint with this project? … No / [Yes]
✔ Would you like to use Tailwind CSS with this project? … No / [Yes]
✔ Would you like to use `src/` directory with this project? … [No] / Yes
✔ Use App Router (recommended)? … No / [Yes]
✔ Would you like to customize the default import alias? … No / Yes

今時はTypeScripは使うだろうし、ESLintも使うだろうから、Enter連打しておけば大丈夫です。ぼくはTailwind.CSSも使うのでこれが最初から選べるのはうれしいですね。

リバースプロキシ設定

API機能を使わないということは別にGoなりJavaなりでAPIサーバーがいるはずですので、そちらにフォワードするように設定を書き換えます。

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
rewrites() { // このメソッドを足す
return [
{
source: "/api/:path*",
destination: "http://localhost:8000/api/:path*"
}
]
}
}

module.exports = nextConfig

別に立ってるAPIサーバーのエミュレーションのために、以下のようなファイルを作ってからプロジェクトのルートで python3 -m "http.server" と実行して8000番でウェブサーバーを立ててみます。

/api/dummy.json
{
"hello": "world"
}

これで、Next.jsの3000番ポートの特定のリクエストはフォワードされるようになります。/apiへのリクエストはバックエンドサーバーに直接行くので、API機能は触る必要はありません。もちろん、最初から別のポートにフロントからリクエストを飛ばす方法もありますが、こちらの方が同一ドメインなので、CORSも不要です。また実際にデプロイするときも同一ドメインであれば追加の設定とかも不要で楽ですよね。

こんな感じの動きになります。

データ取得の実装

データ取得はgetInitialProps()とか、getServerSideProps()とか、getStaticProps()を使わずに、コンポーネントの中から行います。

useEffect()を使って、その中でfetchを使ったり、useSWR()を使ったり。まあ、データ取得のこの方法は新しいapp routerでは標準なので、新規システムの場合は最初からこちらで行っていけば良いです。

静的生成

最後に静的生成です。昔はnext buildを実行してからnext exportをすれば生成されていましたが、今はoutput: 'export'を足すという感じで変わっています。ただ、これだとexportで使えないはずの機能をチェックするエラーチェックでひっかかってしまいます。というのも、rewritesはサーバーがある前提の機能だからです。

next.config.js(これだとエラーになる)
const nextConfig = {
output: 'export', // これを足す
rewrites() { ... }
}

ちょっとトリッキーな方法ですが、ビルド時はrewrite()をなくしてexportにするような設定にすれば両立できます。

next.config.js
module.exports = process.env.npm_lifecycle_event === 'build' ? {
output: 'export'
} : nextConfig

本番ではおそらくLBなどが前段にたっていて、特定のパス(/apiなど)以下はAPIサーバーにフォワードし、そうでない場合はオブジェクトストレージにフォワード。ただし、404ならindex.htmlにフォールバック、みたいな感じにすればNode.jsのサーバーなしにNext.jsアプリケーションがデプロイできます。

名称未設定ファイル-ページ2.drawio.png

これでミニマムな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でアクセスして! って言う方が良いと思います。

API機能

既存のサービスへリクエストを投げてそれをただ返すだけであれば魅力はないと思います。

  1. クレデンシャルをフロントエンドに露出させないでサーバー側の中だけで持つ
  2. バックエンドサーバーのレスポンスの形式がフロントエンドに嬉しくないので整形したい
  3. バックエンドサーバーが複数あるので、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であろう」としているように思えます。

app routerになると、PureなReactにはない、Next.js固有のルールというのは減ります。Reactを学んだ人が、追加で学ぶことも減ります。

ちょっと前にclassmethodさんのNext.jsのブログエントリーも出てましたが、app routerでいらなくなる部分とかを省いたり、最初から設定されているTailwind CSSを前提にしていけばさらに半分ぐらいになるんじゃないかと思います。なので、怖がらなくてもいいし、複雑だからと避ける必要もないな、と思っています。