エラーの発生状況
コーディング作業を終えたばかり、あるいは新しいビルドツールに移行した直後だとしましょう。アプリを確認しようとブラウザをリフレッシュしても、画面は真っ白なままです。ブラウザのコンソールを開くと、そこには不可解な一行が表示されています。
Uncaught SyntaxError: Unexpected token '<'
このエラーは特定のコンポーネントを指し示すことがめったにないため、非常に厄介です。代わりに、サーバーとブラウザ間の根本的な不整合を示唆しています。本質的には、ブラウザがJavaScriptファイルを期待していたのに対し、別のもの(通常はHTMLファイル)を受け取ってしまったことを意味します。
なぜブラウザは「<」を認識しているのか?
React環境において、このエラーが発生する主な原因は通常2つあります。
- JSXのトランスパイル失敗: ブラウザはJSXを直接読み取ることができません。
<App />を標準のJavaScriptに変換せずに実行しようとすると、ブラウザは最初の<に遭遇した時点で即座に停止します。これは、ビルドツール(Babel、Vite、またはSWC)がファイルを正しく処理していない場合に発生します。 - HTMLフォールバックの罠: これが最も頻繁な原因です。
index.htmlが/main.jsをリクエストしていても、サーバーがそのファイルを見つけられない場合、代わりにindex.htmlを返してしまうことがよくあります。HTMLファイルは<!DOCTYPE html>で始まるため、ブラウザはその最初の<をJavaScriptとして解析しようとして失敗します。
クイックフィックス:パスと拡張子の確認
設定ファイルをいじる前に、まずは最もシンプルな解決策から試してみましょう。
1. ネットワークタブを活用する
デベロッパーツール(F12)を開き、**ネットワーク(Network)**タブに移動します。ページをリフレッシュし、bundle.js や main.jsx などのJavaScriptバンドルを探してください。もし 404 Not Found ステータスが表示されているなら、スクリプトのパスが間違っています。失敗したリクエストをクリックして「レスポンス(Response)」サブタブを確認してください。そこにHTMLコードが表示されている場合、サーバーはスクリプトではなくホームページを返しています。
2. Viteにおける厳密なファイル拡張子
ViteはWebpackよりも高速ですが、より厳格です。デフォルトでは、拡張子が .js のファイル内にあるJSXをトランスパイルしません。コンポーネントに <div> タグが含まれているのにファイル名が App.js になっている場合は、App.jsx にリネームしてください。Create React Appから移行した多くの開発者にとって、この変更だけでエラーが解決します。
恒久的な対策:ビルドパイプラインの設定
パスが正しいにもかかわらずブラウザが生のJSXを認識している場合は、トランスパイラの設定を確認する必要があります。
WebpackとBabelの設定
Webpackを使用するモダンなReactプロジェクトでは、babel-loader とReactプリセットが必要です。まず、パッケージがインストールされていることを確認してください。
npm install --save-dev @babel/preset-react babel-loader
babel.config.json を更新して、モダンなJSXトランスフォームを使用するようにします。これにより、すべてのファイルでReactをインポートする必要がなくなります。
{
"presets": [
["@babel/preset-react", {
"runtime": "automatic"
}]
]
}
webpack.config.js で、ローダーが .js と .jsx の両方のファイルを対象としていることを確認します。
module.exports = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: "babel-loader"
}
]
},
resolve: {
extensions: [".js", ".jsx"]
}
};
Viteの設定
ViteでJSXを扱うには、公式のReactプラグインが必要です。「バニラ」のViteプロジェクトを開始して後からReactを追加した場合、これが不足している可能性があります。ターミナルからインストールしてください。
npm install @vitejs/plugin-react --save-dev
次に、vite.config.js にプラグインが含まれていることを確認します。
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()]
})
サーバーサイドのルーティング対応(Express/Nginx)
myapp.com/settings のようなページをリフレッシュしたときにだけエラーが発生しませんか?これは、ブラウザがルートではなく myapp.com/settings/main.js でスクリプトを探してしまうために起こります。スクリプトタグで src="main.js" のような相対パスを使用していると、サブページで動作しなくなります。
解決策: index.html 内のスクリプトソースの先頭にスラッシュを追加し、ブラウザが強制的にルートディレクトリを参照するようにします。
<!-- これは避ける -->
<script src="bundle.js"></script>
<!-- 絶対パスを使用する -->
<script src="/bundle.js"></script>
デバッグと予防
ビルドエラーは、設定ファイル内の小さな構文ミスに潜んでいることがよくあります。package.json や複雑なBabelオブジェクトのトラブルシューティングを行う際、私は ToolCraftのJSON Formatter を使用しています。これは、ビルドツールが設定を黙って無視する原因となる、カンマの欠落やブラケットの不一致をハイライトしてくれます。YAMLベースのCI/CDパイプラインからローカルのJSON設定に構成を戻す場合は、ToolCraftのYAML to JSON converter がロジックの同一性を保つのに役立ちます。
最終確認
- キャッシュを破棄する:
node_modules/.cacheフォルダ(Webpack)またはnode_modules/.vite(Vite)を削除し、サーバーを再起動します。 - ページのソースを確認する: ブラウザでアプリを右クリックし、「ページのソースを表示」を選択します。JavaScriptファイルへのリンクをクリックしてください。もし
<!DOCTYPE html>が表示されたら、サーバーのルーティングが間違っています。もし<div>タグが表示されたら、トランスパイラがスキップされています。

