- Next.js のミドルウェアはロギング設定の自由度が低く、標準ロギング は開発環境でのみ有効なため、本番環境では問題追跡が難しい
- ミドルウェアでは ヘッダー しか受け渡せず、複数ミドルウェアのチェイニングもできないため、複雑なロギング実装が制限される
- AsyncLocalStorage を使ったロギングは Edge ランタイムで予期しない挙動を示し、ページとミドルウェア間の コンテキスト共有 がうまく機能しない
- カスタムサーバー を使ってもロギング問題の解決は難しく、Next.js の設計上の制約が開発者に特定のやり方を強いる
- Vercel の SvelteKit は柔軟なミドルウェアとデータ受け渡しの仕組みを提供しており、Next.js より開発者フレンドリーな設計を示している
Next.js のロギング問題の背景
- Next.js のサービスを運用しながら 本番ログ記録 を試みたところ、標準のログ機能が開発環境でしか有効にならないことに気づいた
- 運用環境に適した ロギングシステム実装 が必要な状況で、Next.js の限界に直面した
ミドルウェアの限界
- 公式ドキュメントによると、ミドルウェアは ルーティング前に実行 され、認証、ロギング、リダイレクトのような機能実装に適している
- 実際にはミドルウェアで 受け渡し可能なパラメータが4つに制限 されており、実質的に受け渡せるのはヘッダーだけ
- 複数のミドルウェアをチェイニング したり組み合わせたりする構造をサポートしていない
- Node.js には Express などで確立されたミドルウェアの慣行が存在するが、Next.js ではそれを十分に適用できない
AsyncLocalStorage による回避の試み
- pino と AsyncLocalStorage を使って、ミドルウェアレベルでロギングインスタンスを管理しようとした
- ミドルウェアでリクエストごとの固有コンテキストにログを保存できるものの、ブラウザ環境でしか正常に動作しない 現象を確認した
- これは Next.js のミドルウェアが基本的に Edge ランタイムを使用 するためであり、nodejs ランタイムに設定してもプロジェクトの状況によっては不安定だった
ページコンポーネントでの問題
- 実際のページやレイアウトコンポーネントでロギング関数を呼び出すと、logger() が null を返す
- ミドルウェアで生成した logger コンテキストが 非同期レンダリングの文脈に渡らない構造的問題 がある
- 解決策は requestId などのロギング情報をヘッダーに載せて渡す 方法しかなく、コードが複雑になり import 構造も混乱する
- クライアントコンポーネントでも同様の構造的分離への追加対応が必要になる
カスタムサーバー導入の試み
- 公式ドキュメントのカスタムサーバー例に従って、http.createServer と next.js の app.getRequestHandler を活用する実験を行った
- この環境で AsyncLocalStorage を再び活用しようとしたが、ミドルウェア–ページ–カスタムサーバー間でコンテキスト連携ができない 現象が繰り返された
- 根本的に Next.js 内部でしか AsyncLocalStorage を正しく使っておらず、開発者には同等の権限が与えられていない
- ミドルウェアからページへ渡せる方法は、事実上 レスポンスヘッダーの変更およびリダイレクト/リライトによる経路変更 しかない
- 利用者の立場では柔軟な拡張やカスタムコンテキストの受け渡しが非常に難しい
SvelteKit との比較
- Vercel の SvelteKit は Next.js より柔軟な ミドルウェアシステム を提供している
- event.locals オブジェクトを通じてリクエストデータを自由に受け渡せる
- 複数の handle 関数 を定義してチェイニングでき、複雑なロジックを実装しやすい
- SvelteKit は 開発者フレンドリー な設計を示しており、Next.js の制約と対照的だ
- SvelteKit は Vercel の製品だが、Next.js より 副次的なプロジェクト と見なされているにもかかわらず、より良い体験を提供している
イシュートラッカーとエコシステム文化への批判
- Next.js の 公式 GitHub イシュートラッカー では、ユーザーフィードバックにほとんど応答しない状況が生じている
- 人気のある issue やバグであっても、長期間にわたり回答や解決なしに放置されることが多い
- ミニマルな再現コードを用意して issue を投稿しても、実質的な応答や後続対応がない
結論と振り返り
- Next.js で見つかる バグや構造的な制約 は繰り返し開発者の生産性を損ない、根本的な改善が必要な様相だ
- 他フレームワーク(SvelteKit など)と比べると、Next.js は主力製品であるにもかかわらず使用性で劣る
- 今すぐ Next.js を置き換える予定は難しいが、今後のプロジェクトでは別の選択肢を検討したい気持ちになった
7件のコメント
Reactが生産性を損なうことには、まだ思い至っていないようですね
今回は個人的な興味だけで、もともと開発していた分野とはまったく関係のない分野であるWeb開発を一度やってみました。next.js v15 app routerで掲示板を作ったのですが……こういう文章を見るたびに、Webのほうは何か新しいことをやってみたいという意欲がなくなっていく気がします。なぜこんなにもエコシステムが不安定なのでしょうか。このままだとまた新しいものが出たらぞろぞろとそちらに移って、少し使ってはまた文句を言いながら別のものを探すのでしょうか。Web開発のほうは本当に難しそうですね。
業界の特性上、変化が速いことは長所でもありますが、短所でもありますよね。ですが本文の問題は、根本的には Vercel のかき回しが原因です。フロントエンドをやるなら、Vercel には少し注意を払う必要があります……
私がキャリアをWebから始めたからかもしれませんが、Webは(特にフロントは)もともとそういうノリ(?)で開発するものです(笑)
目まぐるしく変化する感じ…
JS界隈って、ちょっとそういう感じなんですよね。何か良いとされるものが山ほどあるんですが、少しずつ全部に問題があって、流行に合わせてすぐどんどん変わっていくというか……
自分はJava、EJB、Strutsを主力にやってきたので、そう感じるだけかもしれませんが。
Hacker Newsのコメント
100%同意する。自分も同じ問題を経験したし、今後 Next.js を使うことは二度とない。社内のすべてのチームにも別の代替案を使うよう勧めるつもりだ。
Next.js には、99.9999% のプロジェクトでは不要な巨大な抽象化レイヤーがある。本当にそれが必要なごく少数のケースでは、むしろ下位レベルの部品でカスタムソリューションを作るほうがよいと思う。
自分が使った技術の中で、Next.js が断トツで最悪だった。
自分だけがこう思っているわけではなくて本当に安心した。
Next.js で中程度の複雑さの収益化済みプロダクションアプリを作った。最初は Vercel と Google Firebase を使い、その後セルフホストに移行して Pocketbase に置き換えた。
Pocketbase だけが唯一まともな体験で、それ以外は本当にひどかった。
無限の複雑さ、絶え間ない breaking change、近寄りがたいドキュメントなど、何一つ楽ではなかった。
この 5 年間の FE トレンドを巻き戻して、当時存在していた技術をちゃんと教えることに集中していたら、今よりはるかに良い状況だったはずだと確信している。
複雑な React フロントエンドもいくつか作ったが、React もそれほど好きではないとはいえ、Next.js はさらに深刻だった。
Go とバニラ JS で CMS も作ったことがあるが、DX は少し劣るかもしれないものの、何が起きるのかを自分が実際に把握できているという感覚があった。
なぜ React と Next.js では 6 年たった今でも、常に何が起こるのか推測しなければならないのかわからない。
フレームワークのもつれを収拾できるだけの経験は積んだが、全体としてあまりにも散らかっていて設計が悪いという印象だ。
Go では最初の 6 か月を除けば驚くことはなく、古いコードベースもいまだに堅牢だ。
フロントエンド側でもこういう体験を作れないのが残念だ。
自分の経験では、Next.js の荒削りな部分はバグではなく仕様だ。
すべてが、ユーザーに諦めさせて Vercel ホスティングに縛り付けようとしているように感じる。
今後はさらに悪化すると思う。
いまや PluralSight のようなオンライン講座ですら、React 関連コースで Next.js だけを推している。
なぜこうなったのかはわからないが、ここまで来てしまった。
自分の場合は Sharepoint のほうがもっとひどい記憶なので、Next.js は二番目に最悪だ。
Next.js で最ももどかしいのは、Rails、Wordpress、Meteor のようなフルスタックフレームワークのように何でも提供するふりをしながら、実際には最も面白みがなく制約の多い部分だけに意見を持っていて(ミドルウェア、画像リサイズ、SSR など)、本当に価値のある決定(データベース、ORM、通信プロトコルなど)はユーザーに丸投げしている点だ。
実際には Rails/Wordpress/Meteor とはかなり異なり、Framework がインフラを定義すべきなのに、逆にインフラがフレームワークを支配する状況になってしまっている。
自分のダッシュボードでは "Fluid Active CPU" と "ISR Writes" が上位の使用項目で、毎月 $20 払いながら 100% を超えないことを祈るだけだ。
項目名はスタートレックのような技術用語だらけで、次のメジャーバージョンでまた変わりそうなので学ぶ気にもならない。
かつて Zeit に熱狂していた知人のかなりの数が、結局プロジェクトと顧客を別の場所へ移した。
もし Vercel が次のメジャーリリースで何を変えるべきかと自分に尋ねるなら、「App Router を含め、それ以降のすべての決定が間違っていた」としか言えない。
これをどう立て直せるのかわからない。
Next.js の多くの問題は、コードが実際にどこで実行されるのかをよくわかっていないことに起因していると思う。
ブラウザ、ミドルウェア、edge と node、SSR などの要素が混ざり合って、ものすごい複雑さが生まれている。
これが厄介になるのは次のような場合だ。
グローバルユーザー向けの B2C サービスを運営していて、edge セマンティクスでレイテンシを下げたいとき
Vercel の高額なホスティングを使う覚悟があるとき
バックグラウンドジョブを必要とせず、複雑ではないアーキテクチャだけを求めるとき
それ以外なら、react-vite SPA や Rails のような伝統的 SSR のほうがずっと無難だ。
自分は上の条件にも同意しない。
仮に Next.js に合っていたとしても、生産性と保守性の低下がそれに見合う価値を持つとはまったく思えない。
自分は Gleam の Lustre を使っていて、もう戻るつもりはない。
Elm 創設者のキーノートも、Next.js とは反対の事例だと思う。
https://www.youtube.com/watch?v=sl1UQXgtepE
Vercel は現代の Web の癌だと評したい。
あらゆるフレームワークのエコシステムに入り込み、有料プラン営業のために悪用しながら、オープンソースや競争、Web の発展のためだと装っているだけだ。
一つ目の条件に当てはまる場合でも、Vercel と SSR を使えば性能ボトルネックが解消されるとは思えない。
性能低下の原因の大半は、巨大すぎるバンドルサイズや多数の遅い API 呼び出しなど、もっと基本的なところにある。
基本的なプロファイリング、最適化、単純化などを先に行うほうが、アーキテクチャを複雑化するよりはるかに効果的だ。
「コードがどこで実行されるのかわからない問題」という点には共感する。
以前は JavaScript 万能論が利点だと思っていたが、今ではむしろそれが問題だと感じる。
うちの会社では Inertia.js + Vue を使っているが、全体として構成がシンプルで、フロントレンダリングの力はそのままに、ルーティングは 100% サーバー側で処理し、別個の API も必要ない。
React や Svelte でも Inertia を使える。
最初は Nuxt を使っていたが、バックエンドサーバーとフロントエンドサーバーの 2 つを同時に運用しなければならないほど複雑で、コードがどこで動くのかもわかりにくかった。
今は PHP はサーバー、JS はブラウザという区分のおかげで、まったく悩む必要がない。
これが自分たちには非常に大きな利点だ。
要点がうまく整理されていると思う。
Vercel は React Server Components、Partial Pre-rendering、Edge サーバー、ストリーミングなどで性能最適化を目指している。
独特な設計や API の決定はすべてここに起因している。
必要なケースでは役立つかもしれないが、edge function の一部として SSR を適切に活用するだけでもかなり改善できるはずだ。
フィードバックを残してくれてありがとう。
Middleware における DX の問題は認識しており、15.5 バージョンで Node ランタイム対応を大きな進展として提供した[1]。
もしやり直せるなら、名前を Routing Middleware や Routing Handler のようにして、ルーティング段階で CDN edge へ渡せる上級者向け escape hatch だとわかるものに変えていただろう。
ログが必要なら、OpenTelemetry を使って instrumentation.ts の慣例[2]に従って処理できる。
[1] https://nextjs.org/blog/next-15-5#nodejs-middleware-stable
[2] https://nextjs.org/docs/app/api-reference/file-conventions/instrumentation
返信ありがとう。
でも instrumentation や observability の話を持ち出されると、単なるログの問題をまた別の複雑なレイヤーで解こうとしているように聞こえる。
すべてのアプリに OpenTelemetry が必要なわけではない。
logger().info() のような普通の方法がなぜ使えないのかわからない。
他の言語やフレームワークにはどこにでもあるのに、なぜこれだけこんなに難しいのか理解できない。
ここは空気が否定的なので、先に言っておきたい。Next.js は実際の目的にはよく合った優れたソフトウェアだ。
何百万ものサイトを支えるソフトウェアをよく作ったと思う。
問題は詳細なリファレンスやドキュメントの不足にあり、ドキュメントは何があるかは教えてくれるが、実際にどう使うのか、いつ実行されるのか、よくある落とし穴などはあまり扱っていない。
初心者には親切だが、複雑なランタイムコンテキストや派生する複雑性についての案内が足りない。
これは最近の多くのプロジェクトに見られる傾向だ。
User friendly さと詳しい説明のバランスを取るのは非常に難しい。
今後も改善し続けてほしい。
いわば一言で言えば、
この投稿の著者は、ドメインの違いを把握せずに、どこでも同じように関数を呼ぼうとしていたということだ。
Next.js のエラーは、本来性質の異なるドメインを無理やり一つにまとめようとしたことから生じている。
こうして edge、SSR、Node、クライアントの間を混ぜこぜにしようとすると、複雑さが増すだけだ。
これはドキュメントでも解決できず、むしろ混乱を増やすだけだ。
Reddit のコメントで instrumentation のやり方を勧められたことがある。
同じような時期に opentelemetry を Next に設定していて、たとえドキュメントが違っていても、この体験のあとならブログ記事を書いていただろう。
これはあなたたちのせいではないが、ほとんどすべての opentelemetry パッケージに experimental の表示があって、本番で使うには信頼しにくい。
pino instrumentation の設定もとても大変だった。
pino を serverExternalPackages に追加しないと正しく動かなかった。
自動計測は import の順序にも極端に敏感で、pino の default export しか計測されなかった。
モジュールローカル変数も期待どおりに動かず、globalThis を使う必要があった。
そのうえ結局 https://github.com/vercel/next.js/issues/80445 にも引っかかった。
最終的にはきちんと動いたが、本当に設定しづらかった。
手動ルーター(=vercel/otel を使わない)だったせいで、こういう体験になった。
サーバーサイド middleware をサポートする決定がなされたのなら、なぜいまだに middleware chain(複数関数の接続)ができず、一つしかサポートされないのかわからない。
他のサーバーフレームワークはどれも提供している機能だ。
「複数の middleware chain が不可能」という話が本当なのか疑わしい。
https://nextjs.org/docs/messages/nested-middleware
複数あるなら一つのファイルにまとめて実行しろというのは、本当に信じがたい。
自分の読み方が正しければ、Next.js は複数ファイルで構造化せず一つにまとめろと主張していることになるのか?
スコープの問題で複数ファイルが使いにくいのか。フレームワークとしてはあまりにもひどい要求だ。
こうしたばかげた決定の大半が、フレームワークの利益ではなく、Vercel 側に有利だから下されているのではないかという疑いを拭えない。
Next.js を初めて見たとき、すぐに Meteor.js を思い出した。
個人プロジェクトとしてかなり投資して学んだが、過剰な抽象化と硬直性のせいで、プロトタイプ以上に進めるのは難しかった。
こうした「全部入り」ソリューションはこれからも現れ続ける。設定が楽だからだ。
最近の Hacker News でも Laravel vs Symphony の比較で、複雑になると壊れるという話が交わされていた。
この手のアプローチを、昔の NodeJS/React SPA のように各自で組み立てる方式と比べると、各部品が低レベル抽象化として独立しているぶん柔軟性が高く、一部の差し替えもしやすい。
欠点もあるが、オーバーエンジニアリング(つまり積み上げられた複雑性)よりはスムーズだ。
全部入りが人気なのは十分理解できる。
さまざまなツールやライブラリを組み合わせて互換性を取るのは本当に面倒だからだ。
こうした組み立て方式は、より熟練した人がセットアップしてこそうまく機能する。
自分は asp.net に慣れているが、スタートアップ寄りの開発コミュニティでは評判が悪いものの、実際には非常によく設計されたフレームワークだ。
全部入りではあるが、いつでもフレームワークから抜けて override できたし、フレームワークと戦っていると感じたことは一度もない。
Blazor Server、Blazor Webasm のどちらでもフロントを C# で書けて、どちらも社内向けパネルや SaaS アプリに向いている。
何より、伝統的なサーバーサイドレンダリングでもすべて解決できる。
Web フレームワークに不満がある人には、ぜひ試してほしい。
今ではクロスプラットフォーム対応も優れていて、速度も速く、学びやすい。
学習曲線はあるが、モジュール構造を理解すればすぐに自由に override できた。
他のフレームワークに比べて、限界に突き当たることも本当に少なかった。
NodeJS/React SPA のような各部品を組み立てる方式の要素は、Angular では馴染みが薄い。
Angular はライブラリではなくフレームワークなので、必要な機能の大半が最初からよく揃っている。
(RxJS などは学習曲線が少しあるが、基本を覚えるだけでも十分強力だ。)
普通の SPA では、フレームワークと格闘するような体験はあまりなかった。
ドキュメントやチュートリアル(Tour of Heroes を含む)も非常に優れている。
https://v17.angular.io/tutorial/tour-of-heroes
最新のドキュメントは https://angular.dev/ で確認できる。
Laravel はオーバーエンジニアリングな抽象化の成功例だ。
Laravel のおかげで、本番環境で後悔したことは一度もない。
少し話はそれるが、細かく互換性のないツールやライブラリを延々とつなぎ合わせるのが、まさに自分の仕事そのものだ。
うちのチームは小規模なので、最新化と保守に膨大な時間を費やしており、ずっと前にサポート終了したパッケージの問題も頻繁に起きる。
経験だけでなく、システム構築にかかる初期時間と保守コストは思っているよりずっと大きい。
実際にやってみると、Node でライブラリを継ぎはぎするより Rails のほうが生産性は 10 倍高いと感じた。
問題が生じるのは、フレームワークの根幹や哲学に不満がある場合だけだ(たとえば ActiveRecord が嫌いな場合)。
「複雑性が上がると全部壊れる」というのは、単に技術力が足りないだけだ。
自分は React をかなり擁護する立場で、class コンポーネントから hooks に変わったのも気に入っている。
だが Next.js を使うたびに、いったいどこから間違っているのか見当もつかなくなる。
いろいろなフレームワークや珍しい言語も好きだが、Next.js だけはエラーメッセージの半分も理解できないという体験をする唯一の存在だ。
とくに奇妙な hydration の問題に費やした時間は膨大だった。
自分は React や Next.js を使わないほうだ。
個人的には HTML+CSS にバニラ JS を添えるくらいが好みだ。
シンプルな Next.js のランディングページが Firefox で壊れるのも経験した。
さらにひどいのは、すべてのコンテンツの上に黒い画面と白い文字で "An application client side error has occurred" というメッセージだけが表示されるような失敗の仕方だった。
シンプルなランディングページすら描画できないのかと驚いたし、それが JS フロントフレームワークのせいだとわかったあとには、ただ「まあ、そういうこともあるか」と思った。
ユーザーを説得しようとしている人たちには受け入れられても、業界外の人には戸惑いを与えるかもしれない。
Next はすでに自壊した例だと思う。
VC サイクルを経たところは結局こうなる。
もう使えないし、Vite がデフォルトの選択肢だ。
自分はいつも軽量なソリューションを好む。
Next.js における "middleware" という用語は誤解を招きやすい。
実際には、リクエストがアプリに到達する前に動く軽量な edge function だ(ヘッダーチェック、ルーティング、簡単なガードなど)。
edge ランタイム上で動作し、アプリサーバーとは別の環境だ。
著者も edge とサーバーランタイムを混同しているように見える。
自分も最初は境界が曖昧で混乱したし、JavaScript 中心であるぶん区別がぼやける。
明確なメンタルモデルが必要だと思う。
Next.js の複雑さを責めるのは、ある種、道具箱にハンマー以外の道具も入っていることを責めているような感じだ。
問題は、Next.js の複雑さが自業自得だということだ。
middleware という用語は、ほぼすべてのフレームワークですでに明確な意味が確立されている。
一般に middleware は、ランタイムでリクエスト前に呼ばれる関数のチェーンであり、同じプロセスで実行されるという前提がある。
Next.js はこれを edge に配置し、しかも一つしか許可しない方式にしている。
ほとんどのアプリでは edge 機能まで必要ないので、本当に必要なときだけ opt-in できるようにすべきだ。
道具箱の比喩で言えば、実際に必要な道具だけを追加するのが正しい。
Next.js で "middleware" という用語をこの文脈で使うべきではない。
これは誤用であるだけでなく、用語の乱用でもある。
middleware には Web アプリ業界で長年の定義があるのだから、まったく別のものを指すなら使うべきではなかった。
自分は Next.js の App Router を使っていて満足しているほうだ。
Next.js ではフロントとバックエンドをまたぐのが非常に簡単なので、人々はそこまで抽象化されているのだと勘違いしがちだと思う。
実際にはとても複雑なシステムで、この複雑さは自分で引き受けなければならない。
複雑性が高いからといって、常に遅かったり非生産的だったりするわけではない。
フロントとバックが分離したシステムのほうがずっと扱いやすいが、そのぶん面倒でもある。
React を知っていても Next.js は完全に新しく学ぶ感じで、実際に触ってみないとわからない部分が多い。
それでもある程度慣れれば、フロントとバックエンドを簡単に行き来できる非常に便利なシステムだ。
自分のコメントに downvote した人が多いが、理由を説明してほしい。
自分はいつでも学びたい。
こういう議論では盲目的に否定するだけでなく、ちゃんと議論してほしい。
やっと常識的な意見を見た気がする。
たとえば Python の package/module と Go の module 概念を何も考えずに混同して、Go が悪いと文句を言うようなものだ。
何であれ、使う技術についての基本的な理解は必要だ。
Next.js が App Router に変わってから、まるで express API の改善をブートキャンプ卒業生に任せたように感じた。
すべてのサーバーインターフェース(servlet、rack、plug など)が何年もかけて積み上げてきたロシア人形的な設計と噛み合っているという点で、express API はそれでも成熟したアプローチだ。
ひどい middleware API だけでなく、request パラメータをなくして cookies()/headers() のようなグローバル関数に置き換えたのも奇妙な決定だった。
根本的な設計上の制約が隠れているのだとは思うが、外から見ると、既存のあらゆる教訓を捨てて失敗を繰り返しているように見える。
そこに lowest common denominator な edge ランタイム対応まで絡んで、制約がさらに強まったのだろう。
自分は新しいプロジェクトでは、いつもいろいろなスタックを試すようにしている。
express+react、angular、vue、next、nuxt、go、.net、node、php など、フロントもバックエンドも使ってきた。
ほとんどのフレームワークでは長所と短所が見つかるし、新しく学ぶ面白さもある。
だが Next.js だけは例外で、かなり大きなアプリを作りながら、始まりから終わりまで一つ一つが妙だったり遅かったり不便だったり、信じられない設計だと感じた。
いまも保守しているが、自分が憎んでいる唯一の「何か」だ。
エコシステムは悪くないし人気も高いと言われるが、自分の生の体験としては取り返しがつかないほどネガティブだった。
奇妙だが、現実だ。
Vercel の郵送先住所を知っている人はいる?
この issue は来年には小学校入学の年齢になるので、会社に「学校生活を楽しんでください!」カードでも送りたい。
https://github.com/vercel/next.js/issues/10084
下に付いている Hacker News の意見にある言葉がまさにその通りですね。
"Next.js には 99.9999% のプロジェクトでは不要な巨大な抽象化レイヤーがあり、本当にそういうものが必要な少数のケースでは、むしろ低レベルの部品でカスタムソリューションを作るほうがよいと思う"
無駄に過剰で複雑な API、不安定で不完全なのに平然と production ready だと宣伝するあり方、Vercel への甚大な依存のせいで、Vercel でなければ本格的に運用するのも難しいです。