世界を動かす技術を、日本語で。

Next.jsはイライラする

2025年9月2日原文(blog.meca.sh)

概要

  • Next.jsの本番環境用ロギング設定に苦労した体験談
  • ミドルウェアの制限とAsyncLocalStorageの挙動の問題点
  • ヘッダー経由でしか情報を渡せないNext.jsの設計上の課題
  • カスタムサーバー導入でも根本的な改善は困難
  • SvelteKitなど他フレームワークとの比較とNext.jsへの批判

Next.jsでの本番ロギング地獄

  • Next.js サービスで障害発生、本番環境でのログ取得困難
  • デフォルトのロギングは 開発環境のみ 有効
  • pino ライブラリを選択し、ミドルウェアで導入を試みる
  • ミドルウェアは 4つのパラメータ しか渡せず、 ヘッダーのみ がルートに影響
  • 複数ミドルウェアチェーン 不可という設計上の制約
  • AsyncLocalStorage を利用し、リクエストごとのloggerインスタンス生成
  • ログ出力はできるが、 Edge Runtime のため意図した挙動と異なる
  • Node.js Runtime に切り替えると一部動作するが、プロジェクトによっては不安定

ページ・レイアウトでのロギング問題

  • ページやレイアウトでlogger利用時、 nullが返る 問題発生
  • ミドルウェアと 非同期コンテキスト が一致せず、loggerが共有されない
  • 唯一渡せる情報が ヘッダー のみのため、requestIdをヘッダー経由で伝搬
  • サーバ・ミドルウェア・クライアントで ロギング実装を分割 する必要性
  • import制限 により、サーバ/ミドルウェア間でloggerコード共有不可

カスタムサーバー導入と挫折

  • custom server で独自のロジック実装を試みる
  • AsyncLocalStorage を再利用し、サーバ・ミドルウェア・ページでlogger呼び出し
  • しかし 非同期コンテキストの継続性 が保証されず、loggerが機能しない
  • headers()cookies() はAsyncLocalStorageを使っているが、ユーザーは同じ方法が使えない
  • ミドルウェアからページへ情報を渡す手段は ヘッダーリダイレクト のみ

Next.js設計への批判と他フレームワーク比較

  • Next.js 開発者の「ビジョン」による制約の多さ
  • 日常的に遭遇する「痛み」と ユーザー体験の悪化
  • Vercel 製の SvelteKit では、event.localsで自由にオブジェクトやクラスを渡せる
  • 複数handle関数シーケンス実行 も可能
  • SvelteKit の柔軟性とNext.jsの硬直性の対比
  • Vercel 自身のフラグシップでこの差は納得できないという不満

まとめ・教訓

  • Next.js の本番ロギングは設計上非常に困難
  • ミドルウェアの貧弱さ、 AsyncLocalStorage の制約、情報伝搬手段の乏しさ
  • SvelteKit など、より柔軟なアプローチを持つフレームワークの存在
  • Vercel はNext.jsの改善に本腰を入れるべきという提言
  • この経験が、より良いフレームワーク選択や設計への気づきとなる

Hackerたちの意見

本当にそうだよね。俺はReactの熱心な支持者で、毎日使ってるけど、クラスコンポーネントからフックへの移行は、プログラミングモデルとしては良いと思ってる。でも、Nextを使うと、どこかで道を見失った気がするんだ。いろんなフレームワークを試して、エソテリックなプログラミング言語も好きなんだけど、なぜかNext.jsだけは、エラーメッセージが何を言ってるのか全然わからないことが多い。変なハイドレーションの問題にどれだけ時間を費やしたか、数えきれないよ。

俺はReactやNext.jsのユーザーじゃない。他の人は異論があるかもしれないけど、個人的には、伝統的なHTML+CSSのドキュメントに必要に応じてバニラJSをデコレーションすることで、JSへの接触を最小限に抑えたいと思ってる。シンプルなNext.jsのランディングページがFirefoxで壊れるのを見た時は、ちょっと驚いた。さらに悪いことに、失敗モードはすべてのコンテンツを黒い画面と白い文字で覆って、「アプリケーションクライアントサイドエラーが発生しました」って表示されることだった。シンプルなランディングページがレンダリングできないのは驚きだったけど、その原因がJSのフロントエンドフレームワークだと知った時は、まあそういうもんだなって感じた。支持者には意味があるのかもしれないけど、バンドワゴンに乗ってない俺たちには、本当に混乱することがある。

Nextはかなり前に自分たちをダメにしちゃった。VCサイクルを通るものは、結局そうなるんだよね。彼らとそのお金には嬉しいけど、もう使えないな。今はViteをデフォルトオプションにしてるけど、もっと軽量なものがいいな。

フィードバックを聞いて、感謝してるよ。ミドルウェアのDXの問題はよくわかってる。15.5ではNodeランタイムのサポートに大きな一歩を踏み出して、多くの人が報告してきた問題に対処したんだ。もしタイムマシンがあったら、Routing MiddlewareとかRouting Handlerって呼んでたと思う。ルーティングフェーズでインターセプトするための特定のフックがあって、特定のプロバイダー向けにCDNエッジに届けられるんだ。それに、ちょっとした高度な逃げ道でもある。OPがログについて言及してるから、計測と可観測性のためにOpenTelemetryを取り入れて、instrumentation.tsの規約もあるってことは覚えておいてほしい。

返信ありがとう。でも… > OPがログについて言及してるから、計測と可視化のためにOpenTelemetryを取り入れて、instrumentation.tsの規約を作ったってことは、ちょっと不器用なログ機能の答えが、単にもう一つの重い複雑さのレイヤーを追加することになってるみたいだね。確かに、すべてのアプリケーションがOpenTelemetryを必要とするわけじゃないよね。logger().info()が普通に動けばいいのに。そんなに難しい問題じゃないでしょ?他の言語やフレームワークはみんなやってるのに!

もし本当に適切なサーバーサイドミドルウェアをサポートすることに決めたなら、なぜ他のまともなサーバー実装が提供しているように、ミドルウェア関数を一つだけでなく、チェーンにできないの?

ここにいるから、ちょっと言わせて。この記事では、著者がドメインの違いを理解できず、どこでも同じアプローチで関数を呼び出そうとしている。そりゃうまくいかないよ。Next.jsの誤りは、本質的に異なる関数ドメインを混ぜようとすることだ。そんなことはやめれば大丈夫。ドキュメントは役に立たないし、ただ混乱を招くだけだよ。エッジとSSR、Nodeとクライアントサイドを一つに混ぜるのはめちゃくちゃで、その試みは冗長なフレームワークの複雑さを生むだけなんだ。

アプリルーターに移行した時、まるでブートキャンプを卒業した人たちが「改善」しようとしてるみたいだった。エクスプレスAPIは成熟していて、サーブレットやRack、プラグなどのコンポーザブルなロシアの人形アプローチに大体合ってるのに。ひどいミドルウェアAPIの他に、リクエストパラメータをcookies()やheaders()みたいなグローバル関数に置き換えるという疑わしい決定もあった。もしかしたら、これらの決定が意味を持つような設計上の制約があるのかもしれないけど、正直言って、彼らは苦労して学んだ教訓を全部捨てて、同じ間違いを繰り返してるように見える。

ストリーミングへの執着が新しい制約の大きな要因だと思う。それに、最低限の共通点をサポートすることも。

これらの問題の半分は、コードがどこで実行されているかの相対的な誤解から来てる。Next.jsはブラウザ、ミドルウェア、エッジ、Node、SSRの相互作用によって、層が重なり合ってるから、ものすごく複雑なんだ。これが適用されるのは、次のような状況だけだと思う。* グローバルなオーディエンスにB2C製品を販売しているので、エッジのセマンティクスがレイテンシの問題に役立つ * Vercelに高いプレミアムを払ってホスティングしてもらうことに抵抗がない * バックグラウンドタスク処理が必要ない(Vercelはマーケットプレイスやパートナーサービスを紹介するから)、だから他のプロバイダーでホスティングする必要がない。それ以外の場合は、よく知られた道を進んで、React-ViteのSPAか、Railsの普通のSSRに従った方がいい。

たとえ最初のカテゴリに当てはまっても、VercelとSSRを使うことでパフォーマンスのボトルネックが解決されるとは思えない。みんながやってる他のクレイジーなこと(数メガバイトのバンドルサイズ、DBへの何十回もの往復を伴う遅いAPIコールなど)を考えると、基本的なプロファイリングや最適化、シンプル化をする方が、もっと進展があるように思える。

そう、基本的にはそれだね。Vercelは、React Server Components、部分的なプリレンダリング、エッジサーバー、ストリーミングなどの組み合わせを使って、最適化されたパフォーマンスを解決しようとしてる。彼らの一見変わったデザインやAPIの決定は、基本的にそれに帰結するんだ。必要なら、それが存在するのはいいことだよね。でも、エッジ関数でSSRをやるだけでもかなりのところまで行けるよ。

Hacker Newsで議論の続きを見る