第9章: React Router
React はシングルページアプリケーション (SPA) を構築するためのライブラリですが、 複数の「ページ」間を移動する仕組みは React 本体には含まれていません。 この章では、React の事実上の標準ルーティングライブラリである React Router を使って、 SPA にページ遷移の機能を追加する方法を解説します。 特に、Apache Tomcat にデプロイする際に重要となるルーター選択の考慮事項について詳しく説明します。
SPA とルーティング
従来の Web アプリケーション (MPA: Multi-Page Application) では、ユーザーがリンクをクリックすると ブラウザがサーバーに新しい HTML ファイルをリクエストし、ページ全体が再読み込みされます。 一方、SPA ではアプリケーション全体が単一の HTML ファイル上で動作し、 JavaScript がページの内容を動的に書き換えます。
SPA においてルーティングとは、URL の変化に応じて表示するコンポーネントを切り替える仕組みです。 ブラウザの URL バーにはアドレスが表示されますが、実際にはサーバーへの新しいリクエストは発生しません。 これをクライアントサイドルーティングと呼びます。
| 比較項目 | 従来の MPA | SPA (クライアントサイドルーティング) |
|---|---|---|
| ページ遷移 | サーバーに HTML をリクエスト | JavaScript でコンポーネントを切り替え |
| 画面の更新 | ページ全体が再読み込み | 変更部分のみ更新 |
| 速度 | 毎回サーバー通信が発生 | 初回読み込み後は高速 |
| URL | 各ページに固有の URL | JavaScript で URL を擬似的に管理 |
クライアントサイドルーティングには、ブラウザの「戻る」「進む」ボタンへの対応、 ブックマーク可能な URL の提供、そしてユーザーにとって自然なナビゲーション体験の提供という 重要な役割があります。
react-router-dom のインストール
React Router は複数のパッケージに分かれていますが、Web アプリケーション開発では
react-router-dom を使用します。Vite プロジェクトのルートディレクトリで
以下のコマンドを実行してください。
npm install react-router-dom
インストール後、package.json の dependencies に
react-router-dom が追加されていることを確認しましょう。
{
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router-dom": "^7.1.0"
}
}
BrowserRouter vs HashRouter
React Router には主に 2 種類のルーターが用意されています。 この選択は Tomcat にデプロイする際に極めて重要なので、しっかり理解しましょう。
BrowserRouter
BrowserRouter は HTML5 の History API (pushState, replaceState)
を利用して、クリーンな URL を実現します。
// URL にハッシュ (#) が含まれない、きれいな形式
https://example.com/
https://example.com/about
https://example.com/users/42
https://example.com/contact
URL はきれいですが、大きな問題があります。ユーザーが /about に直接アクセスしたり、
ブラウザをリロードした場合、サーバーは /about に対応する実際のファイルを探します。
SPA では index.html だけが存在するため、サーバーは 404 エラーを返してしまいます。
HashRouter
HashRouter は URL のハッシュ部分 (# 以降) を利用してルーティングを行います。
// URL に # が含まれる形式
https://example.com/#/
https://example.com/#/about
https://example.com/#/users/42
https://example.com/#/contact
ハッシュ (#) 以降の部分はサーバーに送信されないという HTTP の仕様を利用しています。
つまり、どの URL にアクセスしても、サーバーは常に index.html を返します。
これにより、サーバー側の特別な設定なしに SPA ルーティングが機能します。
比較表
| 比較項目 | BrowserRouter | HashRouter |
|---|---|---|
| URL の形式 | /about |
/#/about |
| 見た目 | きれい | # が含まれる |
| サーバー設定 | フォールバック設定が必要 | 設定不要 |
| Tomcat との相性 | web.xml の設定が必要 | そのまま動作する |
| SEO | 有利 | 不利 (ハッシュ以降は無視される) |
| History API | pushState / replaceState | hashchange イベント |
| 直接アクセス・リロード | サーバー設定なしだと 404 | 問題なし |
Tomcat ユーザーへの重要な注意:
Apache Tomcat はデフォルトでは SPA のフォールバック (全てのパスに対して index.html を返す設定) を
サポートしていません。BrowserRouter を使用する場合は、web.xml でエラーページの設定が必要です。
設定に自信がない場合や、手軽にデプロイしたい場合は HashRouter の使用を推奨します。
詳しい設定方法はこの章の最後で解説します。
どちらを選ぶべきか: 社内ツールやプロトタイプなど SEO が不要な場合は HashRouter が手軽です。 公開 Web サイトで SEO が重要な場合は BrowserRouter を選び、サーバー側のフォールバック設定を行いましょう。
基本的なルーティング設定
ルーティングの基本的な設定方法を見ていきましょう。まず、アプリケーション全体をルーターで囲み、 各パスに対応するコンポーネントを定義します。
ページコンポーネントの作成
まず、各ページに対応するコンポーネントを作成します。
function Home() {
return (
<div>
<h1>ホームページ</h1>
<p>React Router のデモアプリへようこそ。</p>
</div>
);
}
export default Home;
function About() {
return (
<div>
<h1>このサイトについて</h1>
<p>React Router を使ったサンプルアプリケーションです。</p>
</div>
);
}
export default About;
function Contact() {
return (
<div>
<h1>お問い合わせ</h1>
<p>お気軽にご連絡ください。</p>
</div>
);
}
export default Contact;
App.jsx でルーティングを設定
Routes コンポーネントの中に Route を配置して、
URL パスとコンポーネントの対応関係を定義します。
ここでは HashRouter を使用する例を示します。
import { HashRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
function App() {
return (
<HashRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</HashRouter>
);
}
export default App;
BrowserRouter を使用する場合は、HashRouter を BrowserRouter に
置き換えるだけです。インポート文も同様に変更してください。
Link と NavLink
SPA では、通常の <a> タグによるリンクではなく、React Router が提供する
Link コンポーネントを使用します。<a> タグを使うと
ページ全体が再読み込みされてしまい、SPA の利点が失われるためです。
Link コンポーネント
import { Link } from 'react-router-dom';
function Navigation() {
return (
<nav>
<Link to="/">ホーム</Link>
<Link to="/about">このサイトについて</Link>
<Link to="/contact">お問い合わせ</Link>
</nav>
);
}
export default Navigation;
Link は内部的には <a> タグをレンダリングしますが、
クリック時のデフォルト動作 (ページ遷移) を防止し、React Router のナビゲーション機能を使って
URL を更新します。
NavLink コンポーネント
NavLink は Link の拡張版で、現在のパスと一致するリンクに
自動的に active クラスを付与します。ナビゲーションメニューで
現在のページをハイライトするのに便利です。
import { NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav className="main-nav">
<NavLink
to="/"
className={({ isActive }) => isActive ? 'nav-active' : ''}
>
ホーム
</NavLink>
<NavLink
to="/about"
className={({ isActive }) => isActive ? 'nav-active' : ''}
>
このサイトについて
</NavLink>
<NavLink
to="/contact"
className={({ isActive }) => isActive ? 'nav-active' : ''}
>
お問い合わせ
</NavLink>
</nav>
);
}
export default Navigation;
className prop に関数を渡すと、isActive プロパティで
現在アクティブかどうかを判定できます。これを使って、アクティブなリンクにスタイルを適用しましょう。
動的ルーティング
URL の一部をパラメータとして受け取ることで、動的なページを作成できます。
例えばユーザー詳細ページ (/users/1, /users/2 など) は、
URL パラメータを使って 1 つのコンポーネントで処理できます。
ルートの定義
<Route path="/users/:id" element={<UserDetail />} />
:id の部分がパラメータです。コロン (:) に続く名前は任意で、
コンポーネント内で useParams フックを使って値を取得できます。
useParams でパラメータを取得
import { useParams } from 'react-router-dom';
function UserDetail() {
const { id } = useParams();
return (
<div>
<h1>ユーザー詳細</h1>
<p>ユーザー ID: {id}</p>
{/* 実際にはこの id を使って API からデータを取得する */}
</div>
);
}
export default UserDetail;
/users/42 にアクセスすると、id の値は文字列 "42" になります。
数値として使用する場合は Number(id) や parseInt(id, 10) で変換してください。
ネストされたルート
実際のアプリケーションでは、共通のレイアウト (ヘッダー、サイドバーなど) を持つページ群を
ネストされたルートで表現します。親ルートにレイアウトコンポーネントを配置し、
子ルートの表示位置を Outlet で指定します。
import { Outlet } from 'react-router-dom';
import Navigation from '../components/Navigation';
function MainLayout() {
return (
<div className="app-layout">
<header>
<h1>My App</h1>
<Navigation />
</header>
<main>
{/* 子ルートのコンポーネントがここに表示される */}
<Outlet />
</main>
<footer>
<p>© 2025 My App</p>
</footer>
</div>
);
}
export default MainLayout;
import { HashRouter, Routes, Route } from 'react-router-dom';
import MainLayout from './layouts/MainLayout';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
import UserDetail from './pages/UserDetail';
function App() {
return (
<HashRouter>
<Routes>
{/* 親ルート: レイアウトを適用 */}
<Route element={<MainLayout />}>
{/* 子ルート: Outlet の位置に表示される */}
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/users/:id" element={<UserDetail />} />
</Route>
</Routes>
</HashRouter>
);
}
export default App;
親の Route に path を指定せず element だけを指定すると、
全ての子ルートに対してそのレイアウトが適用されます。Outlet はプレースホルダーとして機能し、
マッチした子ルートのコンポーネントがその位置にレンダリングされます。
プログラムによるナビゲーション
リンクのクリック以外にも、プログラムからナビゲーションを行いたい場合があります。
例えば、フォーム送信後に別のページへリダイレクトする場合などです。
useNavigate フックを使用します。
import { useNavigate } from 'react-router-dom';
function Login() {
const navigate = useNavigate();
const handleLogin = () => {
// ログイン処理 (API 呼び出しなど)
const success = true;
if (success) {
// ログイン成功後、ダッシュボードに遷移
navigate('/dashboard');
}
};
return (
<div>
<h1>ログイン</h1>
<button onClick={handleLogin}>ログイン</button>
</div>
);
}
export default Login;
useNavigate は navigate 関数を返します。この関数にパスを渡すと、
そのパスへ遷移します。履歴を置き換える (戻るボタンで前のページに戻れなくする) 場合は、
第二引数に { replace: true } を渡します。
// 通常の遷移 (履歴に追加)
navigate('/dashboard');
// 履歴を置き換え (戻るボタンで前のページに戻れない)
navigate('/dashboard', { replace: true });
// 前のページに戻る
navigate(-1);
// 2 ページ前に戻る
navigate(-2);
404 ページ
定義されていないパスにアクセスされた場合に表示する 404 ページを設定しましょう。
path="*" を使うと、他のどのルートにもマッチしなかった場合に
このルートが適用されます。
import { Link } from 'react-router-dom';
function NotFound() {
return (
<div style={{ textAlign: 'center', padding: '2rem' }}>
<h1>404 - ページが見つかりません</h1>
<p>お探しのページは存在しないか、移動された可能性があります。</p>
<Link to="/">ホームに戻る</Link>
</div>
);
}
export default NotFound;
<Routes>
<Route element={<MainLayout />}>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
<Route path="/users/:id" element={<UserDetail />} />
{/* 上記のどのルートにもマッチしない場合 */}
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
Tomcat でのルーティング設定
この章の最も重要なトピックです。React アプリを Tomcat にデプロイする際、 ルーティングの設定方法は使用するルーターによって大きく異なります。
HashRouter を使う場合 (推奨)
HashRouter を使用している場合、Tomcat 側の特別な設定は一切不要です。
ビルドした静的ファイル (dist/ の中身) を Tomcat の webapps/ROOT/
に配置するだけで、ルーティングが正しく機能します。
# 1. ビルド
npm run build
# 2. ビルド成果物を Tomcat に配置
cp -r dist/* /opt/tomcat/webapps/ROOT/
# これだけで完了!追加設定は不要
Tomcat 初心者には HashRouter を推奨します。
URL に # が含まれることを気にしなければ、設定の手間がなく確実に動作します。
社内アプリや管理画面など、SEO を気にしないアプリケーションに最適です。
BrowserRouter を使う場合
BrowserRouter を使用する場合、Tomcat はクライアントサイドのルート (例: /about) に対応する
実際のファイルを見つけられないため、404 エラーを返します。これを解決するには、
web.xml で 404 エラー時に index.html にフォールバックする設定を行います。
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
version="6.0">
<!-- SPA フォールバック: 存在しないパスへのリクエストを index.html に転送 -->
<error-page>
<error-code>404</error-code>
<location>/index.html</location>
</error-page>
</web-app>
この設定により、Tomcat が 404 エラーを検出すると、エラーページの代わりに
index.html を返します。その後、React Router がクライアントサイドで
URL を解析し、適切なコンポーネントを表示します。
注意: この方法では HTTP ステータスコードが 404 のまま index.html が返されます。
ブラウザは問題なくページを表示しますが、厳密には正しい HTTP レスポンスではありません。
これが問題になる場合は、Tomcat の Valve や Filter を使ってリライトする方法もありますが、
設定が複雑になります。シンプルに HashRouter を使うことも検討してください。
ディレクトリ構成 (BrowserRouter の場合)
webapps/
└── ROOT/
├── WEB-INF/
│ └── web.xml <-- フォールバック設定
├── assets/
│ ├── index-xxxxx.js <-- Vite ビルド成果物
│ └── index-xxxxx.css
├── index.html <-- エントリーポイント
└── vite.svg
まとめ:
Tomcat にデプロイする React アプリでは、まず HashRouter の使用を検討してください。
URL のきれいさよりも、確実に動作するシンプルな構成が重要です。
BrowserRouter が必要な場合は、web.xml のエラーページ設定で対応できます。
詳しいデプロイ手順は第12章: ビルドと Tomcat デプロイで解説します。