React Router DOM完全ガイド|ルーティングの基礎から応用まで
React Router DOMの基礎から応用まで網羅的に解説。動的ルート、認証保護、SEO対策、パフォーマンス最適化など実践的な実装方法を詳しく紹介します。
React Router DOMを使っているけど、こんなふうに思ったことはありませんか?
「基本的な使い方は分かるけど、もっと高度な機能を使いたい」 「認証が必要なページの保護方法が分からない」 「SEO対策ってどうすればいいの?」
確かに、React Router DOMは機能が豊富で、すべてを把握するのは簡単ではありませんよね。
この記事では、React Router DOMの基礎から応用まで網羅的に解説します。 動的ルート、認証保護、SEO対策、パフォーマンス最適化まで、実際の開発で役立つテクニックを一緒に学んでいきましょう。 読み終わる頃には、React Router DOMを自在に使いこなせるようになりますよ!
React Router DOMって何?
React Router DOMは、ReactアプリケーションでSPA(Single Page Application)のルーティングを実現するライブラリです。 簡単に言うと、URLの変更に応じて、適切なコンポーネントを表示する仕組みを提供してくれます。
SPAでのルーティングの重要性
従来のWebサイトとReact SPAでは、ページの切り替え方法が大きく違います。
// 従来のWebサイト:各URLで異なるHTMLファイル// /index.html → ホームページ// /about.html → アバウトページ// /products.html → 商品一覧ページ
// React SPA:1つのHTMLで全ページを管理// / → <HomePage />// /about → <AboutPage />// /products → <ProductsPage />// /products/123 → <ProductDetailPage />
この仕組みにより、ページ全体を再読み込みすることなく、スムーズにページを切り替えることができます。 ユーザーにとっても、とても快適な体験になりますね。
基本的なセットアップ
まずは、React Router DOMをプロジェクトにインストールしましょう。
# npm の場合npm install react-router-dom
# yarn の場合yarn add react-router-dom
次に、基本的な設定を行います。
// App.jsimport React from 'react';import { BrowserRouter as Router, Routes, Route, Link, Navigate} from 'react-router-dom';
// ページコンポーネントimport HomePage from './components/HomePage';import AboutPage from './components/AboutPage';import ContactPage from './components/ContactPage';import NotFoundPage from './components/NotFoundPage';
function App() { return ( <Router> <div className="app"> {/* ナビゲーション */} <nav className="navbar"> <div className="nav-brand"> <Link to="/">MyApp</Link> </div> <ul className="nav-links"> <li><Link to="/">ホーム</Link></li> <li><Link to="/about">アバウト</Link></li> <li><Link to="/contact">コンタクト</Link></li> </ul> </nav>
{/* メインコンテンツ */} <main className="main-content"> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/about" element={<AboutPage />} /> <Route path="/contact" element={<ContactPage />} /> {/* リダイレクト */} <Route path="/home" element={<Navigate to="/" replace />} /> {/* 404ページ */} <Route path="*" element={<NotFoundPage />} /> </Routes> </main> </div> </Router> );}
export default App;
このコードでは、基本的なナビゲーションとルーティングを設定しています。
Router
でアプリ全体を囲み、Routes
とRoute
でページの対応関係を定義しているんです。
コア概念の理解
React Router DOMには、いくつかの重要な概念があります。
Router コンポーネント
Routerには、用途に応じていくつかの種類があります。
// BrowserRouter: HTML5 History API を使用(最も一般的)import { BrowserRouter } from 'react-router-dom';
function App() { return ( <BrowserRouter> {/* アプリのコンテンツ */} </BrowserRouter> );}
// HashRouter: URLのハッシュ部分を使用(GitHub Pagesなど)import { HashRouter } from 'react-router-dom';
function App() { return ( <HashRouter> {/* アプリのコンテンツ */} </HashRouter> );}
BrowserRouter
が最も自然なURL形式になるので、通常はこれを使います。
Routes と Route
Routes
の中にRoute
を配置して、URLとコンポーネントの対応関係を定義します。
function App() { return ( <BrowserRouter> <Routes> {/* 完全一致 */} <Route path="/" element={<HomePage />} /> {/* パラメータ付きルート */} <Route path="/users/:id" element={<UserProfile />} /> {/* オプショナルパラメータ */} <Route path="/products/:category?" element={<ProductList />} /> {/* ワイルドカード */} <Route path="/admin/*" element={<AdminLayout />} /> {/* 複数パラメータ */} <Route path="/users/:userId/posts/:postId" element={<PostDetail />} /> </Routes> </BrowserRouter> );}
パラメータやワイルドカードを使うことで、柔軟なルーティングが実現できます。
基本的なナビゲーション
ユーザーがページ間を移動できるように、ナビゲーションを実装しましょう。
Link コンポーネント
Link
コンポーネントは、ページ遷移を行うための基本的なコンポーネントです。
import { Link } from 'react-router-dom';
function Navigation() { return ( <nav> {/* 基本的なLink */} <Link to="/">ホーム</Link> <Link to="/about">アバウト</Link> {/* クラス名付きLink */} <Link to="/products" className="nav-link"> 商品一覧 </Link> {/* state を渡すLink */} <Link to="/contact" state={{ from: 'navigation', source: 'header' }} > お問い合わせ </Link> {/* 外部リンク(通常のaタグを使用) */} <a href="https://example.com" target="_blank" rel="noopener noreferrer"> 外部サイト </a> </nav> );}
Link
を使うことで、ページ全体の再読み込みなしにスムーズな遷移ができます。
NavLink コンポーネント
NavLink
は、現在のページに応じてスタイルを変更できる特別なLink
です。
import { NavLink } from 'react-router-dom';
function Navigation() { return ( <nav className="main-nav"> {/* アクティブ状態を管理するNavLink */} <NavLink to="/" className={({ isActive }) => isActive ? 'nav-link active' : 'nav-link' } end // 完全一致でのみアクティブ > ホーム </NavLink> <NavLink to="/products" className={({ isActive, isPending }) => `nav-link ${isActive ? 'active' : ''} ${isPending ? 'pending' : ''}` } > 商品一覧 </NavLink> <NavLink to="/dashboard" style={({ isActive }) => ({ color: isActive ? '#fff' : '#333', backgroundColor: isActive ? '#007bff' : 'transparent' })} > ダッシュボード </NavLink> </nav> );}
NavLink
を使うと、現在のページがどこなのかが視覚的に分かりやすくなります。
プログラマティックナビゲーション
ボタンクリックやフォーム送信後など、プログラムからページ遷移を行う場合はuseNavigate
を使います。
import { useNavigate, useLocation } from 'react-router-dom';
function LoginForm() { const navigate = useNavigate(); const location = useLocation(); // ログイン前にアクセスしようとしていたページ const from = location.state?.from?.pathname || '/dashboard'; const handleLogin = async (credentials) => { try { await login(credentials); // ログイン成功後、元のページにリダイレクト navigate(from, { replace: true }); } catch (error) { console.error('ログインに失敗しました:', error); } }; const handleCancel = () => { // 前のページに戻る navigate(-1); }; const handleGoHome = () => { // ホームページに移動(履歴に追加) navigate('/'); }; return ( <form onSubmit={handleLogin}> {/* フォームの内容 */} <div className="form-actions"> <button type="button" onClick={handleCancel}> キャンセル </button> <button type="submit"> ログイン </button> </div> </form> );}
useNavigate
を使うことで、様々な場面でプログラムからページ遷移を制御できます。
動的ルートとパラメータ
URLから動的にパラメータを取得して、それに応じてコンテンツを表示する方法を学びましょう。
URL パラメータの基本
URL パラメータを使うと、1つのコンポーネントで複数のページを表現できます。
// ルート定義<Route path="/users/:userId" element={<UserProfile />} /><Route path="/posts/:category/:slug" element={<BlogPost />} /><Route path="/products/:id/reviews/:reviewId?" element={<ProductReview />} />
// コンポーネント内でのパラメータ取得import { useParams } from 'react-router-dom';
function UserProfile() { const { userId } = useParams(); return <div>ユーザーID: {userId}</div>;}
function BlogPost() { const { category, slug } = useParams(); return ( <div> <p>カテゴリ: {category}</p> <p>記事: {slug}</p> </div> );}
このように、URLからパラメータを取得して動的にコンテンツを表示できます。
実用的な動的ルートの例
実際のアプリケーションでよく使われる、商品詳細ページを作ってみましょう。
import React, { useState, useEffect } from 'react';import { useParams, useNavigate, Link } from 'react-router-dom';
// 商品詳細ページfunction ProductDetail() { const { id } = useParams(); const navigate = useNavigate(); const [product, setProduct] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { async function fetchProduct() { try { setLoading(true); setError(null); const response = await fetch(`/api/products/${id}`); if (!response.ok) { throw new Error('商品が見つかりません'); } const productData = await response.json(); setProduct(productData); } catch (err) { setError(err.message); } finally { setLoading(false); } } fetchProduct(); }, [id]); const handleEdit = () => { navigate(`/products/${id}/edit`); }; const handleDelete = async () => { if (window.confirm('本当に削除しますか?')) { try { await fetch(`/api/products/${id}`, { method: 'DELETE' }); navigate('/products', { state: { message: '商品を削除しました' } }); } catch (error) { alert('削除に失敗しました'); } } }; if (loading) return <div className="loading">読み込み中...</div>; if (error) return <div className="error">エラー: {error}</div>; if (!product) return <div className="not-found">商品が見つかりません</div>; return ( <div className="product-detail"> <nav className="breadcrumb"> <Link to="/">ホーム</Link> <span>/</span> <Link to="/products">商品一覧</Link> <span>/</span> <span>{product.name}</span> </nav> <div className="product-content"> <div className="product-images"> <img src={product.mainImage} alt={product.name} /> </div> <div className="product-info"> <h1>{product.name}</h1> <p className="price">¥{product.price.toLocaleString()}</p> <p className="description">{product.description}</p> <div className="product-actions"> <button className="btn btn-primary">カートに追加</button> <button onClick={handleEdit} className="btn btn-secondary"> 編集 </button> <button onClick={handleDelete} className="btn btn-danger"> 削除 </button> </div> </div> </div> </div> );}
このコードでは、URLのパラメータから商品IDを取得し、APIから商品情報を取得して表示しています。 エラーハンドリングやローディング状態の管理も含まれているので、実用的ですね。
クエリパラメータの処理
URL の ?
以降のクエリパラメータを扱う方法も覚えておきましょう。
import { useSearchParams } from 'react-router-dom';
function ProductList() { const [searchParams, setSearchParams] = useSearchParams(); const [products, setProducts] = useState([]); // URLパラメータから値を取得 const category = searchParams.get('category') || ''; const sortBy = searchParams.get('sort') || 'name'; const page = parseInt(searchParams.get('page')) || 1; const search = searchParams.get('q') || ''; // フィルターを更新する関数 const updateFilter = (key, value) => { const newParams = new URLSearchParams(searchParams); if (value) { newParams.set(key, value); } else { newParams.delete(key); } // ページは1にリセット(検索条件変更時) if (key !== 'page') { newParams.set('page', '1'); } setSearchParams(newParams); }; // すべてのフィルターをクリア const clearAllFilters = () => { setSearchParams({}); }; useEffect(() => { // パラメータが変更されたときに商品を取得 async function fetchProducts() { const params = { category, sort: sortBy, page, q: search }; const queryString = new URLSearchParams(params).toString(); const response = await fetch(`/api/products?${queryString}`); const data = await response.json(); setProducts(data.products); } fetchProducts(); }, [category, sortBy, page, search]); return ( <div className="product-list"> <div className="filters"> <div className="filter-group"> <label>検索:</label> <input type="text" value={search} onChange={(e) => updateFilter('q', e.target.value)} placeholder="商品名で検索" /> </div> <div className="filter-group"> <label>カテゴリ:</label> <select value={category} onChange={(e) => updateFilter('category', e.target.value)} > <option value="">すべて</option> <option value="electronics">電子機器</option> <option value="clothing">衣類</option> <option value="books">書籍</option> </select> </div> <button onClick={clearAllFilters} className="btn btn-secondary"> フィルターをクリア </button> </div> <div className="products-grid"> {products.map(product => ( <ProductCard key={product.id} product={product} /> ))} </div> </div> );}
useSearchParams
を使うことで、URLのクエリパラメータを簡単に操作できます。
検索機能やフィルター機能を実装する時にとても便利ですよ。
ネストしたルートとレイアウト
複雑なアプリケーションでは、ルートをネストして階層的な構造を作ることがあります。
基本的なネストルート
ネストルートを使うと、共通のレイアウトを持つページ群を効率的に管理できます。
import { Outlet } from 'react-router-dom';
// アプリのルート設定function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> {/* ダッシュボードのネストルート */} <Route path="/dashboard" element={<DashboardLayout />}> <Route index element={<DashboardOverview />} /> <Route path="analytics" element={<Analytics />} /> <Route path="users" element={<UserManagement />} /> <Route path="settings" element={<Settings />} /> </Route> {/* 管理者用のネストルート */} <Route path="/admin" element={<AdminLayout />}> <Route index element={<AdminDashboard />} /> <Route path="users" element={<AdminUsers />} /> <Route path="products" element={<AdminProducts />} /> <Route path="orders" element={<AdminOrders />} /> </Route> <Route path="*" element={<NotFoundPage />} /> </Routes> </BrowserRouter> );}
この設定により、/dashboard/analytics
にアクセスすると、DashboardLayout
の中にAnalytics
コンポーネントが表示されます。
レイアウトコンポーネント
ネストルートで重要なのが、Outlet
コンポーネントです。
import React from 'react';import { Outlet, NavLink, useLocation, useNavigate } from 'react-router-dom';
function DashboardLayout() { const location = useLocation(); const navigate = useNavigate(); const handleLogout = () => { localStorage.removeItem('authToken'); navigate('/login'); }; return ( <div className="dashboard-layout"> <aside className="sidebar"> <div className="sidebar-header"> <h2>ダッシュボード</h2> </div> <nav className="sidebar-nav"> <NavLink to="/dashboard" end className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`} > <span className="icon">📊</span> 概要 </NavLink> <NavLink to="/dashboard/analytics" className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`} > <span className="icon">📈</span> 分析 </NavLink> <NavLink to="/dashboard/users" className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`} > <span className="icon">👥</span> ユーザー管理 </NavLink> <NavLink to="/dashboard/settings" className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`} > <span className="icon">⚙️</span> 設定 </NavLink> </nav> <div className="sidebar-footer"> <button onClick={handleLogout} className="logout-btn"> ログアウト </button> </div> </aside> <main className="main-content"> <header className="main-header"> <h1>{getPageTitle(location.pathname)}</h1> <div className="header-actions"> <button className="btn btn-primary">新規作成</button> </div> </header> <div className="content-area"> {/* ネストしたルートのコンポーネントがここに表示 */} <Outlet /> </div> </main> </div> );}
function getPageTitle(pathname) { const titles = { '/dashboard': 'ダッシュボード概要', '/dashboard/analytics': 'アナリティクス', '/dashboard/users': 'ユーザー管理', '/dashboard/settings': '設定' }; return titles[pathname] || 'ダッシュボード';}
Outlet
コンポーネントが、子ルートのコンポーネントが表示される場所を指定します。
これにより、共通のレイアウトを保ちながら、コンテンツ部分だけを切り替えることができるんです。
認証とルートガード
実際のアプリケーションでは、ログインが必要なページや、特定の権限を持つユーザーしかアクセスできないページがあります。
認証コンテキスト
まず、認証状態を管理するコンテキストを作成しましょう。
import React, { createContext, useContext, useState, useEffect } from 'react';
const AuthContext = createContext();
export function AuthProvider({ children }) { const [user, setUser] = useState(null); const [isAuthenticated, setIsAuthenticated] = useState(false); const [loading, setLoading] = useState(true); useEffect(() => { checkAuthStatus(); }, []); const checkAuthStatus = async () => { try { const token = localStorage.getItem('authToken'); if (!token) { setLoading(false); return; } const response = await fetch('/api/auth/verify', { headers: { Authorization: `Bearer ${token}` } }); if (response.ok) { const userData = await response.json(); setUser(userData); setIsAuthenticated(true); } else { localStorage.removeItem('authToken'); } } catch (error) { console.error('認証エラー:', error); localStorage.removeItem('authToken'); } finally { setLoading(false); } }; const login = async (credentials) => { try { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(credentials) }); if (response.ok) { const { user, token } = await response.json(); localStorage.setItem('authToken', token); setUser(user); setIsAuthenticated(true); return { success: true }; } else { const errorData = await response.json(); return { success: false, error: errorData.message }; } } catch (error) { return { success: false, error: 'ログインに失敗しました' }; } }; const logout = () => { localStorage.removeItem('authToken'); setUser(null); setIsAuthenticated(false); }; const value = { user, isAuthenticated, loading, login, logout }; return ( <AuthContext.Provider value={value}> {children} </AuthContext.Provider> );}
export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within an AuthProvider'); } return context;}
このコンテキストで、アプリ全体の認証状態を管理します。
プロテクトされたルート
認証が必要なルートを保護するコンポーネントを作成しましょう。
import React from 'react';import { Navigate, useLocation } from 'react-router-dom';import { useAuth } from '../contexts/AuthContext';
// 認証が必要なルートを保護するコンポーネントfunction ProtectedRoute({ children, requiredRole = null, requiredPermissions = [] }) { const { user, isAuthenticated, loading } = useAuth(); const location = useLocation(); // 認証状態をチェック中 if (loading) { return ( <div className="loading-screen"> <div className="loading-spinner"></div> <p>認証確認中...</p> </div> ); } // 未認証の場合、ログインページにリダイレクト if (!isAuthenticated) { return ( <Navigate to="/login" state={{ from: location }} replace /> ); } // 特定の役割が必要で、ユーザーがその役割を持たない場合 if (requiredRole && user.role !== requiredRole) { return ( <Navigate to="/unauthorized" state={{ requiredRole, currentRole: user.role }} replace /> ); } // 特定の権限が必要で、ユーザーがその権限を持たない場合 if (requiredPermissions.length > 0) { const hasAllPermissions = requiredPermissions.every(permission => user.permissions?.includes(permission) ); if (!hasAllPermissions) { return ( <Navigate to="/unauthorized" state={{ requiredPermissions, currentPermissions: user.permissions }} replace /> ); } } // 認証済みで必要な権限を持っている場合、子コンポーネントを表示 return children;}
// 管理者専用ルート保護function AdminRoute({ children }) { return ( <ProtectedRoute requiredRole="admin"> {children} </ProtectedRoute> );}
export { ProtectedRoute, AdminRoute };
これらのコンポーネントを使うことで、簡単にルートを保護できます。
ルート設定での認証保護
実際にルート設定で認証保護を適用してみましょう。
import { AuthProvider } from './contexts/AuthContext';import { ProtectedRoute, AdminRoute } from './components/ProtectedRoute';
function App() { return ( <AuthProvider> <BrowserRouter> <Routes> {/* パブリックルート */} <Route path="/" element={<HomePage />} /> <Route path="/login" element={<LoginPage />} /> <Route path="/register" element={<RegisterPage />} /> {/* 認証が必要なルート */} <Route path="/dashboard" element={ <ProtectedRoute> <DashboardLayout /> </ProtectedRoute> }> <Route index element={<DashboardOverview />} /> <Route path="profile" element={<Profile />} /> <Route path="settings" element={<Settings />} /> </Route> {/* 管理者のみアクセス可能 */} <Route path="/admin" element={ <AdminRoute> <AdminLayout /> </AdminRoute> }> <Route index element={<AdminDashboard />} /> <Route path="users" element={<AdminUsers />} /> <Route path="settings" element={<AdminSettings />} /> </Route> {/* エラーページ */} <Route path="/unauthorized" element={<UnauthorizedPage />} /> <Route path="*" element={<NotFoundPage />} /> </Routes> </BrowserRouter> </AuthProvider> );}
この設定により、認証されていないユーザーが保護されたルートにアクセスしようとすると、自動的にログインページにリダイレクトされます。
パフォーマンス最適化
大規模なアプリケーションでは、パフォーマンスの最適化が重要になります。
遅延読み込み(Lazy Loading)
コンポーネントを遅延読み込みすることで、初期読み込み時間を短縮できます。
import React, { Suspense, lazy } from 'react';import { BrowserRouter, Routes, Route } from 'react-router-dom';
// コンポーネントの遅延読み込みconst HomePage = lazy(() => import('./components/HomePage'));const AboutPage = lazy(() => import('./components/AboutPage'));const ProductsList = lazy(() => import('./components/ProductsList'));const ProductDetail = lazy(() => import('./components/ProductDetail'));const Dashboard = lazy(() => import('./components/Dashboard'));
// 遅延読み込み中に表示するコンポーネントfunction LoadingSpinner() { return ( <div className="loading-container"> <div className="loading-spinner"></div> <p>読み込み中...</p> </div> );}
// エラーバウンダリclass LazyLoadErrorBoundary extends React.Component { constructor(props) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error) { return { hasError: true }; } componentDidCatch(error, errorInfo) { console.error('Lazy loading error:', error, errorInfo); } render() { if (this.state.hasError) { return ( <div className="error-container"> <h2>コンポーネントの読み込みに失敗しました</h2> <button onClick={() => window.location.reload()}> ページを再読み込み </button> </div> ); } return this.props.children; }}
function App() { return ( <BrowserRouter> <LazyLoadErrorBoundary> <Suspense fallback={<LoadingSpinner />}> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/about" element={<AboutPage />} /> <Route path="/products" element={<ProductsList />} /> <Route path="/products/:id" element={<ProductDetail />} /> <Route path="/dashboard" element={<Dashboard />} /> </Routes> </Suspense> </LazyLoadErrorBoundary> </BrowserRouter> );}
遅延読み込みにより、ユーザーが実際にアクセスするページのコンポーネントだけが読み込まれます。 これにより、初期表示が高速化されますよ。
プリロード戦略
ユーザーの操作を予測して、事前にコンポーネントをプリロードする方法もあります。
import { useEffect } from 'react';
// ホバー時にコンポーネントをプリロードfunction SmartLink({ to, children, ...props }) { const handleMouseEnter = () => { // ルートに対応するコンポーネントをプリロード if (to === '/products') { import('../components/ProductsList'); } else if (to === '/dashboard') { import('../components/Dashboard'); } }; return ( <Link to={to} onMouseEnter={handleMouseEnter} {...props}> {children} </Link> );}
// アイドル時間にコンポーネントをプリロードfunction useIdlePreload() { useEffect(() => { const preloadComponents = () => { // よく使用されるコンポーネントをプリロード import('../components/Dashboard'); import('../components/UserProfile'); import('../components/Settings'); }; // requestIdleCallback がある場合は使用 if ('requestIdleCallback' in window) { requestIdleCallback(preloadComponents, { timeout: 5000 }); } else { // なければ setTimeout で代用 setTimeout(preloadComponents, 2000); } }, []);}
function App() { useIdlePreload(); return ( <BrowserRouter> {/* ルート設定 */} </BrowserRouter> );}
このようにプリロード戦略を使うことで、ユーザーが実際にページにアクセスする前にコンポーネントを準備できます。
SEO対策とメタデータ管理
SPAでもSEO対策は重要です。React Helmetを使ってメタデータを動的に管理しましょう。
React Helmet を使用したメタデータ管理
import React from 'react';import { Helmet } from 'react-helmet-async';import { useParams, useLocation } from 'react-router-dom';
// メタデータフックfunction useMetadata(title, description, keywords, image) { const location = useLocation(); const baseUrl = 'https://example.com'; const fullUrl = `${baseUrl}${location.pathname}`; return { title: title ? `${title} | MyApp` : 'MyApp - ホーム', description: description || 'MyApp の説明', keywords: keywords || 'React, JavaScript, Web開発', image: image || `${baseUrl}/default-og-image.jpg`, url: fullUrl };}
// 商品詳細ページでのSEO対策function ProductDetail() { const { id } = useParams(); const [product, setProduct] = useState(null); const metadata = useMetadata( product?.name, product?.description, product?.tags?.join(', '), product?.image ); useEffect(() => { // 商品データを取得 fetchProduct(id).then(setProduct); }, [id]); if (!product) return <div>読み込み中...</div>; return ( <> <Helmet> <title>{metadata.title}</title> <meta name="description" content={metadata.description} /> <meta name="keywords" content={metadata.keywords} /> {/* Open Graph タグ */} <meta property="og:title" content={product.name} /> <meta property="og:description" content={product.description} /> <meta property="og:image" content={product.image} /> <meta property="og:url" content={metadata.url} /> <meta property="og:type" content="product" /> {/* Twitter Card タグ */} <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:title" content={product.name} /> <meta name="twitter:description" content={product.description} /> <meta name="twitter:image" content={product.image} /> {/* 構造化データ */} <script type="application/ld+json"> {JSON.stringify({ "@context": "https://schema.org/", "@type": "Product", "name": product.name, "description": product.description, "image": product.image, "offers": { "@type": "Offer", "price": product.price, "priceCurrency": "JPY", "availability": product.inStock ? "https://schema.org/InStock" : "https://schema.org/OutOfStock" } })} </script> </Helmet> <div className="product-detail"> {/* 商品詳細コンテンツ */} </div> </> );}
React Helmetを使うことで、ページごとに適切なメタデータを設定できます。 これにより、検索エンジンやSNSでの表示が最適化されますよ。
パンくずリストの実装
SEOとユーザビリティの両方に効果的なパンくずリストも実装してみましょう。
// パンくずリストコンポーネントfunction Breadcrumbs() { const location = useLocation(); const pathnames = location.pathname.split('/').filter(x => x); const breadcrumbItems = useMemo(() => { const items = [{ path: '/', label: 'ホーム' }]; let currentPath = ''; pathnames.forEach((pathname, index) => { currentPath += `/${pathname}`; // パスに基づいたラベルの生成 const label = getBreadcrumbLabel(pathname, currentPath); items.push({ path: currentPath, label }); }); return items; }, [pathnames]); return ( <nav className="breadcrumbs" aria-label="パンくずリスト"> <Helmet> <script type="application/ld+json"> {JSON.stringify({ "@context": "https://schema.org", "@type": "BreadcrumbList", "itemListElement": breadcrumbItems.map((item, index) => ({ "@type": "ListItem", "position": index + 1, "name": item.label, "item": `https://example.com${item.path}` })) })} </script> </Helmet> <ol className="breadcrumb-list"> {breadcrumbItems.map((item, index) => ( <li key={item.path} className="breadcrumb-item"> {index < breadcrumbItems.length - 1 ? ( <Link to={item.path}>{item.label}</Link> ) : ( <span aria-current="page">{item.label}</span> )} {index < breadcrumbItems.length - 1 && ( <span className="breadcrumb-separator">/</span> )} </li> ))} </ol> </nav> );}
function getBreadcrumbLabel(pathname, fullPath) { const labels = { 'products': '商品一覧', 'about': 'アバウト', 'contact': 'お問い合わせ', 'dashboard': 'ダッシュボード', 'admin': '管理画面', 'users': 'ユーザー', 'settings': '設定' }; return labels[pathname] || pathname;}
パンくずリストにより、ユーザーは現在位置を把握しやすくなり、検索エンジンもページの階層構造を理解しやすくなります。
まとめ
React Router DOMの基礎から応用まで、幅広く解説しました。
重要なポイント
基本機能
- ルーティングの基本設定
- 動的ルートとパラメータ
- ネストしたルートとレイアウト
- ナビゲーションの実装
応用機能
- 認証とルートガード
- パフォーマンス最適化
- SEO対策とメタデータ管理
- エラーハンドリング
ベストプラクティス
- コード分割による最適化
- 適切な認証保護
- メタデータの動的管理
- アクセシビリティの考慮
実装時のポイント
- 段階的な実装を心がける
- ユーザー体験を最優先に考える
- SEO対策を忘れずに実装
- パフォーマンスを常に意識する
今日から始められること
- 基本的なルーティング設定の見直し
- 遅延読み込みの導入
- 認証保護の実装
- メタデータ管理の改善
React Router DOMは、現代のReactアプリケーション開発において欠かせないライブラリです。 この記事で学んだ技術を活用して、より良いユーザー体験とSEO効果を持つアプリケーションを構築してみてください。
継続的な学習と実践により、React Router DOMを完全にマスターし、プロフェッショナルなWeb開発者としてのスキルを向上させていきましょう!