ビルドと Tomcat デプロイ
React アプリケーションを本番環境で公開するには、開発用のソースコードを ブラウザが直接実行できる形式に変換(ビルド)し、それを Web サーバーに配置する必要があります。 この章では、Vite によるビルドの仕組みから Apache Tomcat へのデプロイ方法まで、 本番運用に必要な知識をすべて解説します。
ビルドとは
開発中は Vite の開発サーバーが JSX の変換やモジュールの解決をリアルタイムで行ってくれますが、 本番環境ではそのような処理を動的に行うサーバーは存在しません。 ビルドとは、開発用のソースコードを本番環境向けに最適化された静的ファイル群に変換するプロセスです。
ビルドプロセスでは主に以下の処理が行われます。
| 処理 | 説明 |
|---|---|
| バンドリング (Bundling) | 複数の JavaScript / CSS ファイルを少数のファイルにまとめます。これによりブラウザの HTTP リクエスト数が減り、読み込みが高速化します。 |
| トランスパイル (Transpile) | JSX を通常の JavaScript に変換し、最新の ES 構文を幅広いブラウザで動作する形式に変換します。 |
| ミニファイ (Minification) | 空白・改行・コメントを除去し、変数名を短縮してファイルサイズを削減します。 |
| ツリーシェイキング (Tree Shaking) | 実際に使用されていないコード(デッドコード)を自動的に除去します。ライブラリの一部の関数だけを import している場合、使っていない関数はバンドルに含まれません。 |
| コード分割 (Code Splitting) | アプリケーションを複数のチャンクに分割し、必要な部分だけを遅延読み込みできるようにします。 |
| アセット処理 | 画像の最適化、CSS の結合・ミニファイ、フォントの処理などを行います。 |
Vite は内部でビルドツール Rollup を使用しています。 開発時は ESM(ES Modules)をネイティブに利用して高速な HMR(Hot Module Replacement)を実現し、 ビルド時は Rollup による最適化されたバンドルを生成します。
npm run build
ビルドは npm run build コマンドで実行します。
このコマンドは package.json の scripts セクションに定義されている
vite build を実行します。
# プロジェクトのルートディレクトリで実行
npm run build
ビルドが成功すると、以下のような出力が表示されます。
vite v6.x.x building for production...
✓ 142 modules transformed.
dist/index.html 0.46 kB │ gzip: 0.30 kB
dist/assets/index-DmK4x7q2.css 1.42 kB │ gzip: 0.73 kB
dist/assets/index-CfR3lNmz.js 144.78 kB │ gzip: 46.62 kB
✓ built in 1.23s
package.json の scripts セクションを確認してみましょう。
Vite で作成したプロジェクトにはデフォルトで build スクリプトが含まれています。
{
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
}
}
npm run preview を実行すると、ビルド成果物をローカルでプレビューできます。
Tomcat にデプロイする前に、ビルド結果をローカルで確認するのに便利です。
デフォルトでは http://localhost:4173 で起動します。
ビルド成果物の構成
npm run build を実行すると、プロジェクトルートに dist/ ディレクトリが生成されます。
このディレクトリが Tomcat に配置する成果物のすべてです。
dist/
├── index.html ← エントリポイント(SPA のメイン HTML)
├── assets/
│ ├── index-CfR3lNmz.js ← バンドルされた JavaScript(ハッシュ付き)
│ ├── index-DmK4x7q2.css ← バンドルされた CSS(ハッシュ付き)
│ ├── vendor-Bk7a9xN4.js ← React 等のライブラリ(コード分割時)
│ └── logo-a1b2c3d4.svg ← import した画像アセット
└── favicon.ico ← public/ に置いたファイルはそのままコピー
各ファイルの役割を詳しく見てみましょう。
index.html
SPA のエントリポイントです。ビルドされた JavaScript と CSS への参照が自動的に挿入されています。 ブラウザはまずこのファイルを読み込み、JavaScript が実行されることでアプリケーション全体が描画されます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My React App</title>
<script type="module" crossorigin src="/assets/index-CfR3lNmz.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DmK4x7q2.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
ハッシュ付きファイル名
index-CfR3lNmz.js のようにファイル名にハッシュ値が含まれています。
これはファイルの内容から計算されたハッシュで、コードが変更されるとハッシュ値も変わります。
この仕組みにより、ブラウザのキャッシュを確実に無効化(キャッシュバスティング)できます。
public/ ディレクトリのファイル
public/ ディレクトリに配置したファイル(favicon.ico, robots.txt など)は
ビルド時にそのまま dist/ のルートにコピーされます。
ハッシュ化やバンドルの対象にはなりません。
vite.config.js の設定
Tomcat にデプロイする際に最も重要な設定が base オプションです。
これはビルドされたアセットの参照パス(ベースパス)を指定します。
デプロイ先の URL 構造に合わせて正しく設定する必要があります。
base の設定を誤ると、デプロイ後にページが真っ白になります。 これは最も多いデプロイ失敗の原因です。デプロイ先の URL に合わせて必ず正しく設定してください。
ROOT デプロイの場合(base: '/')
Tomcat の webapps/ROOT/ に配置する場合、アプリケーションはドメイン直下
(例: http://example.com/)でアクセスされます。
この場合は base をデフォルトの '/' のままにします。
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
base: '/', // デフォルト値なので省略可
})
サブディレクトリデプロイの場合(base: '/myapp/')
Tomcat の webapps/myapp/ に配置する場合、アプリケーションは
http://example.com/myapp/ でアクセスされます。
この場合は base にコンテキストパスを設定します。
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
base: '/myapp/', // Tomcat のコンテキストパスに合わせる
})
base の値は必ず スラッシュで始まりスラッシュで終わる ようにしてください。
例: '/myapp/' (正しい)、'myapp' (誤り)、'/myapp' (末尾スラッシュなしは避ける)。
base を設定すると、生成される index.html 内のアセット参照パスが変わります。
<script src="/assets/index-CfR3lNmz.js"></script>
<link href="/assets/index-DmK4x7q2.css">
<script src="/myapp/assets/index-CfR3lNmz.js"></script>
<link href="/myapp/assets/index-DmK4x7q2.css">
開発時とビルド時で base を切り替える
開発時は /、本番時は /myapp/ としたい場合は、
環境変数やコマンドライン引数を使って切り替えられます。
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig(({ command }) => ({
plugins: [react()],
base: command === 'build' ? '/myapp/' : '/',
}))
Tomcat へのデプロイ手順
ビルドが完了したら、dist/ ディレクトリの中身を Tomcat に配置します。
配置方法は大きく 2 つあります。
方法 1: ROOT デプロイ
アプリケーションをドメイン直下(http://example.com/)で公開する場合は、
dist/ の中身を Tomcat の webapps/ROOT/ にコピーします。
-
vite.config.js の base を確認
base: '/'(デフォルト)になっていることを確認します。 -
ビルドを実行
ターミナル
npm run build -
既存の ROOT ディレクトリをバックアップ(任意)
ターミナル
# Tomcat のインストールディレクトリに移動 cd $CATALINA_HOME # 既存の ROOT をバックアップ mv webapps/ROOT webapps/ROOT_backup -
dist/ の中身を ROOT にコピー
ターミナル
# ROOT ディレクトリを作成 mkdir -p webapps/ROOT # ビルド成果物をコピー cp -r /path/to/your-project/dist/* webapps/ROOT/ -
Tomcat を起動(または再起動)
ターミナル
# Linux / macOS $CATALINA_HOME/bin/startup.sh # Windows %CATALINA_HOME%\bin\startup.bat -
ブラウザで確認
http://localhost:8080/にアクセスして、React アプリが表示されれば成功です。
方法 2: サブディレクトリデプロイ
アプリケーションをサブディレクトリ(http://example.com/myapp/)で公開する場合は、
dist/ の中身を webapps/myapp/ にコピーします。
-
vite.config.js の base を設定
base: '/myapp/'を設定してからビルドします。vite.config.jsexport default defineConfig({ plugins: [react()], base: '/myapp/', }) -
ビルドを実行
ターミナル
npm run build -
dist/ の中身を myapp/ にコピー
ターミナル
# webapps 内にアプリ名のディレクトリを作成 mkdir -p $CATALINA_HOME/webapps/myapp # ビルド成果物をコピー cp -r /path/to/your-project/dist/* $CATALINA_HOME/webapps/myapp/ -
Tomcat を起動(または再起動)
Tomcat が起動中であれば自動デプロイが有効な場合はディレクトリ配置だけで認識されます。
-
ブラウザで確認
http://localhost:8080/myapp/にアクセスして確認します。
Tomcat の webapps ディレクトリ構造
参考として、デプロイ後の Tomcat のディレクトリ構造を示します。
$CATALINA_HOME/
└── webapps/
├── ROOT/ ← ドメイン直下 (http://host:8080/)
│ ├── index.html
│ ├── assets/
│ │ ├── index-xxx.js
│ │ └── index-xxx.css
│ └── WEB-INF/ ← BrowserRouter 使用時に必要
│ └── web.xml
├── myapp/ ← サブディレクトリ (http://host:8080/myapp/)
│ ├── index.html
│ ├── assets/
│ │ ├── index-xxx.js
│ │ └── index-xxx.css
│ └── WEB-INF/
│ └── web.xml
└── manager/ ← Tomcat Manager(デフォルト)
Tomcat の自動デプロイ機能(autoDeploy="true")が有効な場合、
webapps/ にディレクトリを配置するだけで自動的にアプリケーションとして認識されます。
再起動は不要です。デフォルト設定では自動デプロイが有効になっています。
HashRouter を使う場合
React Router の HashRouter を使用している場合、Tomcat 側での追加設定は不要です。
ビルドした dist/ の中身をそのまま配置するだけで動作します。
HashRouter は URL の #(ハッシュ)以降の部分でルーティングを行います。
http://example.com/#/ ← ホーム
http://example.com/#/about ← About ページ
http://example.com/#/users/123 ← ユーザー詳細ページ
ブラウザは # 以降の部分をサーバーに送信しません。
そのため、どの URL にアクセスしても Tomcat は常に index.html を返し、
クライアント側の React Router が # 以降を解析してルーティングを行います。
この仕組みにより、サーバー設定なしで SPA のルーティングが動作します。
import { HashRouter, Routes, Route } from 'react-router-dom'
function App() {
return (
<HashRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</HashRouter>
)
}
HashRouter は設定が簡単で Tomcat との相性が良いですが、URL に # が含まれるため
SEO には不向きです。管理画面や社内ツールなど SEO が不要なアプリケーションに適しています。
SEO が重要な場合は次のセクションで解説する BrowserRouter を使用してください。
BrowserRouter を使う場合
BrowserRouter は通常の URL パス(http://example.com/about)でルーティングを行います。
URL が綺麗になりますが、Tomcat 側で追加の設定が必要です。
問題: ブラウザリロード時の 404 エラー
BrowserRouter を使用した SPA で /about にアクセスすると、
ブラウザは Tomcat に /about というリソースを要求します。
しかし Tomcat 上に /about というファイルやディレクトリは存在しないため、
404 エラーが返されます。
この問題を解決するには、存在しないパスへのリクエストをすべて index.html に転送し、
React Router にルーティングを任せる必要があります。
解決策 1: web.xml でエラーページをリダイレクト
最もシンプルな方法は、Tomcat の WEB-INF/web.xml で 404 エラーを
index.html にリダイレクトすることです。
-
WEB-INF ディレクトリを作成
デプロイ先に
WEB-INFディレクトリを作成します。ターミナル# ROOT デプロイの場合 mkdir -p $CATALINA_HOME/webapps/ROOT/WEB-INF # サブディレクトリデプロイの場合 mkdir -p $CATALINA_HOME/webapps/myapp/WEB-INF -
web.xml を作成
以下の内容で
WEB-INF/web.xmlを作成します。WEB-INF/web.xml<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" version="6.0"> <error-page> <error-code>404</error-code> <location>/index.html</location> </error-page> </web-app> -
動作を確認
Tomcat を再起動し、
/aboutなどのルートに直接アクセスしてページが正常に表示されることを確認します。
この方法の仕組み:
- ブラウザが
/aboutをリクエスト - Tomcat は
/aboutに対応するファイルが見つからず 404 エラーを生成 web.xmlのerror-page設定により、index.htmlが返される- ブラウザが
index.htmlを読み込み、JavaScript が実行される - React Router がブラウザの URL(
/about)を解析し、対応するコンポーネントを表示
この方法では HTTP ステータスコードが 404 のまま index.html の内容が返されます。
通常のブラウザ利用では問題ありませんが、ステータスコード 200 を返したい場合は
次の Servlet フィルター方式を使用してください。
解決策 2: Servlet フィルターで URL リライト
より正確な制御が必要な場合は、カスタム Servlet フィルターを使って URL リライトを行います。
この方法では、静的ファイルへのリクエストはそのまま処理し、
それ以外のリクエストのみ index.html にフォワードします。
ステータスコードも正常な 200 が返されます。
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
public class SpaRedirectFilter implements Filter {
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String path = req.getRequestURI();
// 静的ファイル(拡張子あり)やAPIリクエストはそのまま処理
if (path.contains(".") || path.startsWith("/api/")) {
chain.doFilter(request, response);
return;
}
// それ以外は index.html にフォワード
request.getRequestDispatcher("/index.html")
.forward(request, response);
}
}
フィルターを web.xml に登録します。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0">
<filter>
<filter-name>SpaRedirectFilter</filter-name>
<filter-class>com.example.SpaRedirectFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>SpaRedirectFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
Servlet フィルター方式を使う場合、コンパイル済みの .class ファイルを
WEB-INF/classes/com/example/ ディレクトリに配置する必要があります。
あるいは、JAR ファイルとしてパッケージし WEB-INF/lib/ に配置します。
シンプルな SPA 配信のみが目的であれば、解決策 1 の error-page 方式で十分です。
BrowserRouter の basename 設定
サブディレクトリにデプロイする場合、BrowserRouter にも basename を設定する必要があります。
import { BrowserRouter, Routes, Route } from 'react-router-dom'
function App() {
return (
<BrowserRouter basename="/myapp">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
)
}
サブディレクトリデプロイ時は、3 つの設定をすべて揃える必要があります。
vite.config.jsのbase: '/myapp/'BrowserRouterのbasename="/myapp"- Tomcat の
webapps/myapp/ディレクトリ名
これらが一致しないと、ルーティングやアセットの読み込みが正しく動作しません。
context.xml の設定
必要に応じて、Tomcat の context.xml でアプリケーション固有の設定を行えます。
META-INF/context.xml をデプロイディレクトリに配置します。
<?xml version="1.0" encoding="UTF-8"?>
<Context reloadable="false">
<!-- 静的ファイルのキャッシュ設定(10日間) -->
<Resources cachingAllowed="true"
cacheMaxSize="100000"
cacheTtl="864000000" />
</Context>
主要な設定項目を確認しましょう。
| 属性 | 説明 | 推奨値 |
|---|---|---|
reloadable |
クラスファイルの変更を監視して自動再読み込みするか | false(静的ファイルのみなので不要) |
cachingAllowed |
静的リソースのキャッシュを有効にするか | true |
cacheMaxSize |
キャッシュの最大サイズ(KB) | 100000(約 100 MB) |
React SPA は基本的に静的ファイルの配信のみなので、context.xml の設定は
最小限で問題ありません。特に要件がなければ省略しても構いません。
環境変数の管理
Vite では .env ファイルを使って環境変数を管理できます。
VITE_ プレフィックスが付いた変数のみがクライアントサイドのコードに公開されます。
セキュリティ上の注意: VITE_ プレフィックスが付いた変数は
ビルド後の JavaScript に平文で埋め込まれます。
API キーやパスワードなどの秘密情報は絶対に VITE_ 変数に設定しないでください。
.env ファイルの種類
| ファイル名 | 読み込みタイミング | 用途 |
|---|---|---|
.env |
常に読み込み | 全環境共通のデフォルト値 |
.env.local |
常に読み込み(.gitignore に追加推奨) | ローカル固有の設定 |
.env.development |
npm run dev 時 |
開発環境の設定 |
.env.production |
npm run build 時 |
本番環境の設定 |
実践例: API エンドポイントの切り替え
# 開発環境 — Vite の開発サーバーのプロキシを使用
VITE_API_BASE_URL=http://localhost:5173/api
# 本番環境 — Tomcat 上の API サーバー
VITE_API_BASE_URL=https://api.example.com
アプリケーションコード内では import.meta.env でアクセスします。
const API_BASE = import.meta.env.VITE_API_BASE_URL;
export async function fetchUsers() {
const response = await fetch(`${API_BASE}/users`);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
return response.json();
}
ビルド時に .env.production の値が JavaScript に直接埋め込まれます。
例えば import.meta.env.VITE_API_BASE_URL は
ビルド後のコードでは "https://api.example.com" という文字列に置換されます。
実行時に動的に変更することはできないため、環境ごとに別々にビルドする必要があります。
キャッシュ対策
Web アプリケーションを更新した際に、ユーザーのブラウザが古いキャッシュを使い続ける問題は よくあるトラブルです。Vite はこの問題をコンテンツハッシュによって自動的に解決します。
コンテンツハッシュの仕組み
Vite が生成するファイル名にはハッシュ値が含まれています。
# v1.0(初回ビルド)
assets/index-CfR3lNmz.js
assets/index-DmK4x7q2.css
# v1.1(コードを変更して再ビルド)
assets/index-Ax8bPq1K.js ← ハッシュが変わる
assets/index-DmK4x7q2.css ← CSS は変更なしなので同じハッシュ
ファイルの内容が変わるとハッシュ値も変わるため、ブラウザは新しいファイルとして認識し、 自動的に最新版をダウンロードします。内容が変わっていないファイルはハッシュが同じなので、 キャッシュがそのまま利用され、無駄なダウンロードが発生しません。
index.html のキャッシュ制御
index.html 自体はハッシュ化されないため、ブラウザがキャッシュする可能性があります。
Tomcat の web.xml でキャッシュヘッダーを設定することで対策できます。
<web-app ...>
<!-- BrowserRouter 用の 404 フォールバック -->
<error-page>
<error-code>404</error-code>
<location>/index.html</location>
</error-page>
<!-- index.html のキャッシュを無効化 -->
<filter>
<filter-name>NoCacheFilter</filter-name>
<filter-class>org.apache.catalina.filters.ExpiresFilter</filter-class>
<init-param>
<param-name>ExpiresDefault</param-name>
<param-value>access plus 0 seconds</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>NoCacheFilter</filter-name>
<url-pattern>/index.html</url-pattern>
</filter-mapping>
<!-- アセットファイルに長期キャッシュを設定 -->
<filter>
<filter-name>AssetsCacheFilter</filter-name>
<filter-class>org.apache.catalina.filters.ExpiresFilter</filter-class>
<init-param>
<param-name>ExpiresDefault</param-name>
<param-value>access plus 1 year</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>AssetsCacheFilter</filter-name>
<url-pattern>/assets/*</url-pattern>
</filter-mapping>
</web-app>
index.html はキャッシュ無効化(access plus 0 seconds)、
assets/ 配下のハッシュ付きファイルは長期キャッシュ(access plus 1 year)
が最適な設定です。ハッシュ付きファイルは内容が変われば名前が変わるため、
長期キャッシュでも古いファイルが使われる心配がありません。
デプロイの自動化
手動でのコピー作業は手間がかかるうえミスも起こりやすいため、 シェルスクリプトやCI/CDパイプラインでデプロイを自動化することをおすすめします。
シェルスクリプトによる自動デプロイ
#!/bin/bash
# React アプリを Tomcat にデプロイするスクリプト
# 使い方: ./deploy.sh [root|myapp]
set -e # エラー時に即座に終了
# === 設定 ===
CATALINA_HOME="/opt/tomcat"
PROJECT_DIR="/home/user/my-react-app"
DEPLOY_TARGET="${1:-ROOT}" # デフォルトは ROOT デプロイ
# === ビルド ===
echo "=== ビルドを開始します ==="
cd "$PROJECT_DIR"
npm ci # クリーンインストール
npm run build # プロダクションビルド
if [ ! -d "dist" ]; then
echo "エラー: dist ディレクトリが見つかりません"
exit 1
fi
# === デプロイ ===
DEPLOY_DIR="$CATALINA_HOME/webapps/$DEPLOY_TARGET"
echo "=== デプロイ先: $DEPLOY_DIR ==="
# 既存のデプロイを日付付きでバックアップ
if [ -d "$DEPLOY_DIR" ]; then
BACKUP_DIR="${DEPLOY_DIR}_backup_$(date +%Y%m%d_%H%M%S)"
echo "バックアップ: $BACKUP_DIR"
mv "$DEPLOY_DIR" "$BACKUP_DIR"
fi
# ビルド成果物をコピー
mkdir -p "$DEPLOY_DIR"
cp -r dist/* "$DEPLOY_DIR/"
# WEB-INF/web.xml をコピー(BrowserRouter 使用時)
if [ -f "tomcat/web.xml" ]; then
mkdir -p "$DEPLOY_DIR/WEB-INF"
cp tomcat/web.xml "$DEPLOY_DIR/WEB-INF/"
echo "WEB-INF/web.xml をコピーしました"
fi
echo "=== デプロイが完了しました ==="
echo "URL: http://localhost:8080/$( [ \"$DEPLOY_TARGET\" = \"ROOT\" ] && echo '' || echo \"$DEPLOY_TARGET/\" )"
# 実行権限を付与
chmod +x deploy.sh
# ROOT にデプロイ
./deploy.sh
# myapp にデプロイ
./deploy.sh myapp
CI/CD での自動デプロイ
GitHub Actions や Jenkins などの CI/CD ツールを使えば、 Git への push をトリガーにビルドとデプロイを完全に自動化できます。 基本的な流れは以下の通りです。
- ソースコードを Git リポジトリに push
- CI/CD パイプラインが起動
npm ciで依存関係をインストールnpm run buildでビルドnpm testでテストを実行- テスト成功後、ビルド成果物を Tomcat サーバーにデプロイ(SCP / rsync / Docker など)
本番デプロイ前に npm run preview でビルド成果物をローカルで確認する習慣をつけましょう。
ビルド時にのみ発生する問題(環境変数の未設定、base パスの誤りなど)を事前に発見できます。
トラブルシューティング
Tomcat へのデプロイでよく遭遇する問題と解決策をまとめます。
| 症状 | 原因 | 解決策 |
|---|---|---|
| ページが真っ白 | vite.config.js の base がデプロイ先と一致していない |
デプロイ先のコンテキストパスに合わせて base を修正し、再ビルドする |
| リロードすると 404 | BrowserRouter を使用しているが、サーバー側のフォールバック設定がない |
WEB-INF/web.xml に error-page を設定するか、HashRouter に切り替える |
| CSS / JS が読み込めない | アセットの参照パスが間違っている(base の設定ミス) |
ブラウザの開発者ツール(Network タブ)で実際のリクエストパスを確認し、base を修正する |
| 画像が表示されない | public/ 内の画像パスが base を考慮していない |
絶対パスの場合は base を含めるか、import を使って画像を参照する |
| API で CORS エラー | フロントエンドとバックエンドのオリジン(ドメイン・ポート)が異なる | バックエンド側で CORS ヘッダーを設定するか、Tomcat のリバースプロキシを設定する |
| 環境変数が undefined | VITE_ プレフィックスが付いていない、または .env.production がない |
変数名が VITE_ で始まっていることを確認し、.env.production を作成する |
| 古いバージョンが表示される | ブラウザが index.html をキャッシュしている |
Ctrl+Shift+R で強制リロード、またはキャッシュヘッダーを設定する |
| Tomcat 起動時にエラー | web.xml の記述ミス(タグの閉じ忘れ、名前空間の誤りなど) |
catalina.out のログを確認し、web.xml の構文を検証する |
問題が発生したら、まずブラウザの開発者ツールを開いてください。 Console タブでエラーメッセージ、Network タブで失敗しているリクエスト(赤字の行)を確認すると、 原因の特定が大幅に早くなります。
本番環境のチェックリスト
本番環境にデプロイする前に、以下のチェックリストを確認しましょう。
| カテゴリ | チェック項目 | 説明 |
|---|---|---|
| ビルド | 本番ビルドを使用している | npm run build で生成した dist/ を使用していること。開発サーバーをそのまま公開しないこと。 |
| ビルド | base パスが正しい |
vite.config.js の base がデプロイ先のコンテキストパスと一致していること。 |
| ビルド | 環境変数が本番用に設定されている | .env.production で API エンドポイント等が本番環境向けになっていること。 |
| ルーティング | SPA フォールバックが設定されている | BrowserRouter 使用時は web.xml で 404 フォールバックを設定していること。 |
| セキュリティ | HTTPS が有効 | SSL/TLS 証明書を設定し、HTTPS でのアクセスを強制していること。 |
| セキュリティ | セキュリティヘッダーが設定されている | X-Content-Type-Options、X-Frame-Options、Content-Security-Policy 等を設定していること。 |
| セキュリティ | 機密情報がコードに含まれていない | API キーやパスワードが JavaScript バンドルに埋め込まれていないこと。 |
| パフォーマンス | Gzip / Brotli 圧縮が有効 | Tomcat の server.xml で compression="on" を設定し、転送サイズを削減していること。 |
| パフォーマンス | キャッシュヘッダーが適切 | index.html はキャッシュ無効化、assets/ は長期キャッシュが設定されていること。 |
| パフォーマンス | バンドルサイズが適切 | npm run build の出力でファイルサイズを確認し、不要な依存関係がないこと。 |
| 運用 | エラーページが設定されている | 500 エラー等の場合にユーザーフレンドリーなページが表示されること。 |
| 運用 | ログ設定が適切 | Tomcat のアクセスログが有効になっており、問題の追跡が可能なこと。 |
| 運用 | バックアップ・ロールバック手順がある | デプロイに問題があった場合に、前のバージョンに素早く戻せる手順があること。 |
Gzip 圧縮の設定
Tomcat で Gzip 圧縮を有効にするには、server.xml の Connector 設定に
compression 属性を追加します。
<Connector port="8080"
protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
compression="on"
compressionMinSize="1024"
compressibleMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json" />
Gzip 圧縮により、JavaScript と CSS のファイルサイズが通常 60-70% 削減されます。
ビルド出力に表示される gzip の数値が、圧縮後の実際の転送サイズの目安です。
まとめ
React アプリを Tomcat にデプロイする際の全体的な流れを振り返りましょう。
vite.config.jsでbaseを正しく設定する.env.productionで本番用の環境変数を設定するnpm run buildでビルドするdist/の中身を Tomcat のwebapps/にコピーするBrowserRouter使用時はWEB-INF/web.xmlを設置する- ブラウザで動作を確認する
適切に設定すれば、React SPA と Tomcat の組み合わせは安定した本番環境を構築できます。 トラブルが発生した場合は、このページのトラブルシューティングセクションとチェックリストを参照してください。