【ITニュース解説】`next dev` Renders but Nothing Works: Your CSP Is Missing `unsafe-eval`
2026年08月24日に「Dev.to」が公開したITニュース「`next dev` Renders but Nothing Works: Your CSP Is Missing `unsafe-eval`」について初心者にもわかりやすく解説しています。
ITニュース概要
Next.js開発環境でCSPを設定すると、ページは表示されるが機能が動作しない問題が発生する。これは、開発サーバーがモジュール実行に`eval`を使うため、CSPに`'unsafe-eval'`がないとJavaScriptが実行できないからだ。本番では不要だが開発では必須なので、開発時のみ`'unsafe-eval'`をCSPに追加するか、本番ビルドで確認すると良い。
ITニュース解説
システムエンジニアを目指す皆さんにとって、Webアプリケーションの開発は日々の試行錯誤の連続です。特にNext.jsのようなフレームワークを使うと、高速な開発体験が得られる反面、思わぬ落とし穴にはまることもあります。今回は、そんな開発中に遭遇しやすい、しかし原因特定が非常に難しい問題の一つについて解説します。
ある日、Next.jsで開発しているWebアプリケーションの作業中に、不思議な現象が起きました。開発サーバーを起動するnext devコマンドを実行すると、Webページは普段通りにブラウザに表示されます。デザインもデータも完璧で、見た目上は何も問題がないように見えます。しかし、実際にページを操作しようとすると、何も反応しないのです。検索ボックスに文字を入力してもフィルタリングされず、並べ替えボタンをクリックしても表示順が変わらず、「もっと見る」ボタンを押しても何も表示が変わりません。ページ上のあらゆるインタラクティブな要素が、まるで死んだように機能しません。
この状況の厄介な点は、通常発生するようなエラーメッセージが一切表示されないことです。ブラウザの開発者ツールを開いても、エラーページが出たり、赤いオーバーレイが表示されたり、ネットワークタブでリクエストが失敗したりするような明らかな兆候がありません。サーバーからは正しくHTMLが送られ、ページはレンダリングされています。ただ、HTMLが送られただけで、JavaScriptによって動的に「命を吹き込む」処理(これを「ハイドレーション」と呼びます)が全く行われていない状態です。そのため、多くの開発者はまず自分の書いたコンポーネントにバグがあるのではないかと疑いますが、原因は全く別のところにありました。
この問題の真犯人は、ブラウザのコンソールにひっそりと表示されている、一見すると見過ごしてしまいそうな一行のエラーメッセージでした。それは、「Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: "script-src 'self' 'unsafe-inline' …"」というものです。このメッセージが厄介なのは、一般的なJavaScriptのエラーメッセージのように、どのファイルやどのコードが原因なのかを具体的に指し示さないことです。スタックトレースもなく、ただ「コンテンツセキュリティポリシー(CSP)の指定により、JavaScriptの文字列評価が拒否されました」とだけ告げるため、初見では戸惑うことでしょう。
では、この「コンテンツセキュリティポリシー(CSP)」とは何でしょうか。これは、Webサイトのセキュリティを強化するための仕組みの一つです。Webサイトを閲覧する際に、どこからスクリプトを読み込むか、どこへ接続できるかなど、様々なリソースの読み込みや操作を細かく制限することで、悪意のある攻撃(例えばクロスサイトスクリプティング攻撃)からユーザーを保護します。このポリシーは、Webサーバーから送られるHTTPヘッダーの一部としてブラウザに伝えられ、ブラウザはその指示に従ってページの挙動を制限します。今回のエラーメッセージにあるscript-srcは、どのソースからJavaScriptファイルを読み込んで良いかを指定するディレクティブ(指示)です。そしてunsafe-evalは、JavaScriptのeval()関数のように、文字列として与えられたコードをJavaScriptとして実行することを許可する特別なキーワードです。セキュリティ上はリスクがあるため、デフォルトでは許可されません。
なぜこの問題が開発環境でのみ発生し、本番環境では問題なく動作していたのでしょうか。その鍵は、Next.jsの開発サーバーの仕組みにあります。next devコマンドで起動する開発サーバーは、開発効率を上げるための便利な機能、「Hot Module Replacement(HMR)」や「React Refresh」をサポートしています。これらは、コードを変更した際にページ全体をリロードすることなく、変更された部分だけを即座にブラウザに反映させるための機能です。この機能を実現するために、Next.jsの開発サーバーはコンパイルされたモジュールをブラウザに送信する際に、それらをeval関数を使ってJavaScriptの文字列として実行します。これにより、変更されたコンポーネントだけを動的に置き換えることが可能になります。一方、next buildコマンドで作成される本番用のビルドは、最適化された静的なファイル群を出力します。本番環境では、これらのファイルを直接ブラウザが読み込むため、evalのような文字列評価は行われません。そのため、本番環境のCSPにunsafe-evalが含まれていなくても、問題なく動作するわけです。
この「開発環境では動かないのに本番では動く」というギャップが、今回の問題を特定しづらくする大きな要因となります。CI/CDパイプライン(継続的インテグレーション/継続的デリバリー)やプレビュー環境では通常、本番用のビルドが実行されるため、開発環境でのみ発生するこの問題は検出されません。結果として、開発者がローカルで作業している最中に突如として発生し、自分の書いたコードのバグを疑って無駄な時間を費やすことになってしまいます。
では、開発環境にもCSPが適用されてしまうのはなぜでしょうか。Next.jsアプリケーションのCSPは通常、next.config.mjsファイル内でHTTPヘッダーとして設定されます。この設定ファイルは、特に指示しない限り、開発モードか本番モードかを区別しません。source: '/:path*'のようにすべてのパスに適用されるように記述すると、next devで起動した開発サーバーも、next startで起動した本番サーバーと同じセキュリティヘッダーをブラウザに送出します。これは一般的に合理的なデフォルト設定です。開発中に、最終的にデプロイされる本番環境と同じセキュリティ設定でテストしたいと考えるからです。しかし、今回のunsafe-evalのように、開発環境固有の挙動が必要な場合には、このデフォルト設定が裏目に出てしまうことがあります。
この問題への対処法は主に二つあります。
一つ目の解決策は、開発環境でのみCSPを緩和するという方法です。具体的には、next.config.mjsファイル内で、Node.jsの環境変数process.env.NODE_ENVが'development'である場合に限り、script-srcディレクティブに'unsafe-eval'を追加します。さらに、HMRソケット(開発サーバーとブラウザ間のリアルタイム通信)のために、connect-srcディレクティブにws:やwss:(WebSocketのプロトコル)を開発環境でのみ追加する必要がある場合もあります。この方法の重要な注意点は、なぜこの条件分岐が必要なのかをコメントで明確に記述することです。セキュリティヘッダーの条件付き設定は、後からコードをレビューする人が「一貫性がない」と判断し、安易に削除してしまう可能性があるため、その理由をしっかり残しておくことが不可欠です。このアプローチは、開発の快適さ(Fast Refreshなど)を維持しながら、セキュリティと開発のギャップを埋める良い選択肢と言えます。
二つ目の解決策は、クライアントサイドの挙動をnext devでテストするのをやめ、常にnext build && next startでビルドして本番環境に近い状態でテストするというものです。この方法は、例えばCloudflare WorkersのようなNext.jsとは異なるランタイム環境にデプロイする場合に特に有効です。next devで確認した挙動が、実際のデプロイ環境で全く異なる、という事態を避けることができます。しかし、この方法にはコストが伴います。コードを変更するたびにアプリケーション全体を再ビルドする必要があるため、Fast Refreshの恩恵を受けられず、開発速度が著しく低下します。もし本番環境がNode.jsベースであるならば、一つ目の解決策の方が開発体験の観点からは優れています。しかし、本番環境が特殊な場合、このアプローチは必要不可欠なものとなるでしょう。
今回の問題から得られる最も重要な教訓は、next.configで設定されたセキュリティヘッダーが開発サーバーにも適用され、開発サーバーはデプロイされる本番環境とは異なる独自の要件を持っているという点です。unsafe-evalは、このようにサイレントな失敗を引き起こすため、原因特定に最も時間がかかるケースの一つですが、他にもconnect-srcでHMRソケットに必要なws:やwss:の追加、あるいはstyle-srcでCSSの取り扱い方によってunsafe-inlineが必要になるなど、開発環境と本番環境で異なる要件を持つセキュリティディレクティブは他にも存在します。
Next.jsアプリケーションにCSPを追加しようとしているシステムエンジニア志望の皆さんにとって、最も迅速で確実なチェック方法は、コードレビューではありません。まずはnext devコマンドで開発サーバーを起動し、Webページを開きます。次に、ページ上のインタラクティブな要素(ボタンや検索ボックスなど)をいくつかクリックしてみてください。そして、ブラウザの開発者コンソールを開き、表示される最初の数行のメッセージを注意深く確認するのです。JavaScriptのエラーメッセージではなく、今回のようなCSPに関する警告やエラーが最初に表示される可能性があります。このわずか30秒ほどの確認作業が、「ページが表示された」状態と「ページが完全に機能している」状態を区別する唯一のテストであり、見た目は全く同じ「動かないページ」と「動くページ」の決定的な違いを見抜く手助けとなるでしょう。この知識は、今後のWeb開発において皆さんの大きな力となるはずです。