第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.jsondependenciesreact-router-dom が追加されていることを確認しましょう。

package.json (抜粋)
{
  "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 を実現します。

BrowserRouter の 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 のハッシュ部分 (# 以降) を利用してルーティングを行います。

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 を選び、サーバー側のフォールバック設定を行いましょう。

基本的なルーティング設定

ルーティングの基本的な設定方法を見ていきましょう。まず、アプリケーション全体をルーターで囲み、 各パスに対応するコンポーネントを定義します。

ページコンポーネントの作成

まず、各ページに対応するコンポーネントを作成します。

src/pages/Home.jsx
function Home() {
  return (
    <div>
      <h1>ホームページ</h1>
      <p>React Router のデモアプリへようこそ。</p>
    </div>
  );
}

export default Home;
src/pages/About.jsx
function About() {
  return (
    <div>
      <h1>このサイトについて</h1>
      <p>React Router を使ったサンプルアプリケーションです。</p>
    </div>
  );
}

export default About;
src/pages/Contact.jsx
function Contact() {
  return (
    <div>
      <h1>お問い合わせ</h1>
      <p>お気軽にご連絡ください。</p>
    </div>
  );
}

export default Contact;

App.jsx でルーティングを設定

Routes コンポーネントの中に Route を配置して、 URL パスとコンポーネントの対応関係を定義します。 ここでは HashRouter を使用する例を示します。

src/App.jsx
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 を使用する場合は、HashRouterBrowserRouter に 置き換えるだけです。インポート文も同様に変更してください。

SPA では、通常の <a> タグによるリンクではなく、React Router が提供する Link コンポーネントを使用します。<a> タグを使うと ページ全体が再読み込みされてしまい、SPA の利点が失われるためです。

Link コンポーネント

src/components/Navigation.jsx
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 コンポーネント

NavLinkLink の拡張版で、現在のパスと一致するリンクに 自動的に active クラスを付与します。ナビゲーションメニューで 現在のページをハイライトするのに便利です。

src/components/Navigation.jsx
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 つのコンポーネントで処理できます。

ルートの定義

src/App.jsx (抜粋)
<Route path="/users/:id" element={<UserDetail />} />

:id の部分がパラメータです。コロン (:) に続く名前は任意で、 コンポーネント内で useParams フックを使って値を取得できます。

useParams でパラメータを取得

src/pages/UserDetail.jsx
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 で指定します。

src/layouts/MainLayout.jsx
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>&copy; 2025 My App</p>
      </footer>
    </div>
  );
}

export default MainLayout;
src/App.jsx (ネストされたルート)
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;

親の Routepath を指定せず element だけを指定すると、 全ての子ルートに対してそのレイアウトが適用されます。Outlet はプレースホルダーとして機能し、 マッチした子ルートのコンポーネントがその位置にレンダリングされます。

プログラムによるナビゲーション

リンクのクリック以外にも、プログラムからナビゲーションを行いたい場合があります。 例えば、フォーム送信後に別のページへリダイレクトする場合などです。 useNavigate フックを使用します。

src/pages/Login.jsx
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;

useNavigatenavigate 関数を返します。この関数にパスを渡すと、 そのパスへ遷移します。履歴を置き換える (戻るボタンで前のページに戻れなくする) 場合は、 第二引数に { replace: true } を渡します。

useNavigate の使い方
// 通常の遷移 (履歴に追加)
navigate('/dashboard');

// 履歴を置き換え (戻るボタンで前のページに戻れない)
navigate('/dashboard', { replace: true });

// 前のページに戻る
navigate(-1);

// 2 ページ前に戻る
navigate(-2);

404 ページ

定義されていないパスにアクセスされた場合に表示する 404 ページを設定しましょう。 path="*" を使うと、他のどのルートにもマッチしなかった場合に このルートが適用されます。

src/pages/NotFound.jsx
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;
src/App.jsx (404 ルート追加)
<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/ に配置するだけで、ルーティングが正しく機能します。

デプロイ手順 (HashRouter)
# 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 にフォールバックする設定を行います。

WEB-INF/web.xml
<?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 の場合)

Tomcat のディレクトリ構成
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 デプロイで解説します。