はじめに
本記事では、React Router v7のフレームワーク機能を使用してブログサイトを構築する際の基本的な使い方から、複雑なルーティング例までを幅広く解説します。この記事を読むことで、単純なページ遷移から動的ルーティング、さらにはネストされたルートの設定まで習得することができます。
初心者の方からステップアップを目指す方まで、ぜひ一緒にReact Router v7の世界を探ってみましょう。
React Router とは
React Routerは、Reactアプリケーション向けのルーティングライブラリです。ルーティングとは、ユーザーが特定のURLにアクセスした際に、どのコンポーネントを表示するかを制御する仕組みのことです。
React Routerはv7よりReactフレームワークとして最大限に使用することも、独自のアーキテクチャを持つライブラリとして最小限に使用することもできます。
フレームワーク利用とは?
React Routerはv7よりReactフレームワークとして活用することも可能となりました。
具体的には下記のような機能を提供しています。
- Vite バンドラーと開発サーバーの統合
- ホットモジュールの置換
- コード分割
- 型の安全性を確保したファイルシステムまたは設定ベースのルーティング
- 型の安全性を確保したデータロード
- 型の安全性を確保したアクション
- アクション後のページデータの自動再バリデーション
- SSR、SPA、静的レンダリング戦略
- 保留状態と楽観的 UI
- デプロイメントアダプター
今回ご紹介する機能はこちらが中心となります。
ブログサイトを構築する前提でこれらの機能をどう活用していくか詳しくご紹介していきます。
ライブラリ利用とは?
これまでのバージョン(v6)以前のバージョンはシンプルで宣言的なルーティング・ライブラリとして使用することができます。
URLとコンポーネントのセットをマッチングして、URLデータへのアクセスを提供し、アプリ内をナビゲートすることとなります。
これまでv6を利用してきたユーザーはv7にバージョンアップしたのち引き続きライブラリとしてReact Routerを使うことが多いかと思います。
React Router v7のライブラリ利用について詳しく知りたい方はこちらをご参考ください。
今回の記事のゴール
React Router v7のフレームワーク機能を使って下記のページにそれぞれ遷移できるようルーティングの設定を中心に実装します。
-
トップページ
-
投稿
-
マイページ
-
アカウント
-
設定画面
-
それぞれのページにアクセスしやすいようにナビゲーションを作成し遷移できるようにしています。
セットアップ
React Routerが用意しているテンプレートを使ってプロジェクトを作成していきます。
mkdir react-router-v7-framework-tutorial
cd react-router-v7-framework-tutorial
npx create-react-router@latest .
これでコードのセットアップから依存パッケージのダウンロードまで完了しました。
下記コマンドで起動します。
npm run dev
プロジェクトにあるREADME.mdを見てみましょう。
- 🚀 Server-side rendering
- ⚡️ Hot Module Replacement (HMR)
- 📦 Asset bundling and optimization
- 🔄 Data loading and mutations
- 🔒 TypeScript by default
- 🎉 TailwindCSS for styling
- 📖 [React Router docs](https://reactrouter.com/)
これらの機能が既に使える状態で用意されています。
またpackage.json
を確認するとReactのバージョンが最新の19になっており、19の機能を考慮したフレームワークとなっています。
"dependencies": {
"@react-router/node": "^7.1.3",
"@react-router/serve": "^7.1.3",
"isbot": "^5.1.17",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-router": "^7.1.3"
},
ルーティング
プロジェクトを起動したときに画面が表示されたように既にトップページのルーティングがセットアップ段階で行われています。
import { type RouteConfig, index } from "@react-router/dev/routes";
export default [index("routes/home.tsx")] satisfies RouteConfig;
トップにアクセスが来た場合にroutes/home.tsx
をレンダリングする設定となっています。
フレームワークとして利用する場合はこのapp/routes.ts
がルーティングを担うメインのファイルとなります。
import type { Route } from "./+types/home";
import { Welcome } from "../welcome/welcome";
export function meta({}: Route.MetaArgs) {
return [
{ title: "New React Router App" },
{ name: "description", content: "Welcome to React Router!" },
];
}
export default function Home() {
return <Welcome />;
}
2つエクスポートされていますがデフォルトエクスポートしている箇所がレンダリングしている部分となります。
export default function Home() {
return <Welcome />;
}
実態としては../welcome/welcome.tsx
となります。
なんとなくapp
ディレクトリの構造が見えてきたところでまずはトップを修正していきます。
import type { Route } from "./+types/home";
- import { Welcome } from "../welcome/welcome";
export function meta({}: Route.MetaArgs) {
return [
{ title: "New React Router App" },
{ name: "description", content: "Welcome to React Router!" },
];
}
export default function Home() {
- return <Welcome />;
+ return (
+ <h1>
+ トップ
+ </h1>
+ )
}
welcome
は削除してトップ
という表示を出してみます。
トップだけだと少し寂しいのでブログ記事を表示してみます。
export const posts = [
{
id: 1,
title: 'React Router 7のセットアップ',
description: 'セットアップ以外にも様々な機能をご紹介',
},
{
id: 2,
title: 'Reactでの状態管理方法',
description: 'useState、useContextなどをご紹介',
},
{
id: 3,
title: 'コンポーネント設計のベストプラクティス',
description: 'ディレクトリ構成についてもご紹介',
},
]
export const getPostById = (id: number) => {
return posts.find((post) => post.id === id)
}
本来であればデータベースやCMSなどでブログ情報を管理して取得することとなりますが今回の記事からかなり脱線するので簡単なデータをファイルで用意しました。
3件のブログ情報とそのブログ情報をIDで取得できる関数を用意しました。
別のアーキテクチャになりますがデータベースやCMSなどでデータ取得するチュートリアルも過去に作成していますので興味ありましたらこちらもご参考ください。
話を戻します。
ブログデータが用意できましたのでトップに表示します。
+ import { posts } from '~/const/posts'
import type { Route } from './+types/home'
export function meta({}: Route.MetaArgs) {
return [
{ title: 'New React Router App' },
{ name: 'description', content: 'Welcome to React Router!' },
]
}
export default function Home() {
return (
+ <>
<h1>トップ</h1>
+ <ul>
+ {posts.map((post) => (
+ <li key={post.id}>{post.title}</li>
+ ))}
+ </ul>
+ </>
)
}
先ほど作成したブログデータのタイトルがトップページに表示されました。
動的セグメント
次はブログの個別記事を参照するための設定をしていきたいと思います。
ホームでブログ記事のタイトルをクリックするとブログの個別記事のページに遷移するというものです。
まずはルーティングの設定から。
- import { type RouteConfig, index } from '@react-router/dev/routes'
+ import { type RouteConfig, index, route } from '@react-router/dev/routes'
export default [
index('routes/home.tsx'),
+ route('post/:postId', './routes/post.tsx'),
] satisfies RouteConfig
post/:postId
でブログの個別記事を表示するよう設定しました。postId
はブログデータのID
を想定しています。こちらが動的セグメントという機能になります。
次に表示を行うコンポーネントを作成します。
import { getPostById } from '~/const/posts'
import type { Route } from './+types/post'
export default function Post({ params }: Route.ComponentProps) {
const post = getPostById(Number(params.postId))
if (!post) {
return <div>投稿が見つかりませんでした。</div>
}
return (
<div>
<h1>{post.title}</h1>
{post.description}
</div>
)
}
import type { Route } from './+types/post'
ルーティングを設定すると自動で生成される型定義になります。
export default function Post ({
params,
}: Route.ComponentProps) {
params
が使えるようになります。
エディタ上でも確認できますが、params.postId
でURLで渡ってきたpostId
を取得できます。
const post = getPostById(Number(params.postId))
postId
を使ってブログデータを1件取得します。
if (!post) {
return <div>投稿が見つかりませんでした。</div>
}
return (
<div>
<h1>{post.title}</h1>
{post.description}
</div>
)
取得結果によってレンダリングをおこなっています。
ブログデータに存在しないID
でリクエストが発生した場合を考慮して投稿が見つかりませんでした。
という表示も出せるようにしています。
ブログデータが見つかった場合はtitle
, description
を表示します。
それでは実際に動作確認を行ってみます。
ブログデータが存在する場合
ブログデータが存在しない場合
最後にブログ一覧からブログ個別記事に遷移できる設定を行います。
import { posts } from '~/const/posts'
import type { Route } from './+types/home'
+ import { Link } from 'react-router'
export function meta({}: Route.MetaArgs) {
return [
{ title: 'New React Router App' },
{ name: 'description', content: 'Welcome to React Router!' },
]
}
export default function Home() {
return (
<>
<h1>トップ</h1>
<ul>
{posts.map((post) => (
- <li key={post.id}>{post.title}</li>
+ <li key={post.id}>
+ <Link to={`/post/${post.id}`}>{post.title}</Link>
+ </li>
))}
</ul>
</>
)
}
React Router
のLink
を使用するとクリックした時にto
で設定したURLに遷移できます。
一通り設定が完了しましたので実際にホーム画面から遷移できるか確認してみます。
少しだけブログサイトっぽくなってきました。
ネストしたルート
今度はブログサイトにマイページ画面を作成しようと思います。
マイページではアカウント情報を見る画面と設定変更をできる画面を作成します。
import { type RouteConfig, index, route } from '@react-router/dev/routes'
export default [
index('routes/home.tsx'),
route('post/:postId', './routes/post.tsx'),
+ route('mypage', './routes/mypage/index.tsx', [
+ route('account', './routes/mypage/account.tsx'),
+ route('settings', './routes/mypage/settings.tsx'),
+ ]),
] satisfies RouteConfig
ルートはネストが可能となっています。
/mypage
でアクセスすると./routes/mypage/index.tsx
を表示します。
app/routes/mypage/index.tsx
ファイルを作成してみましょう。
import { Outlet } from 'react-router'
export default function MyPage() {
return (
<>
<h1>マイページ</h1>
<Outlet />
</>
)
}
Outlet は、React Router のコンポーネントで、ネストされたルートのコンテンツを表示するためのプレースホルダー です。
具体的には、親ルート に共通のレイアウトやコンポーネント (例: ヘッダーやサイドバー) を定義しつつ、その中に 子ルート のコンテンツを埋め込むために使用されます。
では子ルートにあたる、account.tsx
とsettings.tsx
を作成します。
export default function Account() {
return <h2>アカウント</h2>
}
export default function Settings() {
return <h2>設定画面</h2>
}
このように設定することでマイページという一つのグループにアカウントや設定画面があるという構成ができます。
それでは実際にネストされたルートにアクセスできるか確認します。
まずは/mypage
「マイページ」という文言が表示されています。
次に/mypage/account
すると親ルートにあたる「マイページ」という文言と子ルートにあたる「アカウント」という文字が表示されることが確認できます。
Outlet
が正常に動いて子ルートを表示できていることがわかります。
このようなルーティングの設定でも動くのですが幾つか便利な機能がありますので合わせて紹介します。
- import { type RouteConfig, index, route } from '@react-router/dev/routes'
+ import { type RouteConfig, index, layout, prefix, route } from '@react-router/dev/routes'
export default [
index('routes/home.tsx'),
route('post/:postId', './routes/post.tsx'),
- route('mypage', './routes/mypage/index.tsx', [
- route('account', './routes/mypage/account.tsx'),
- route('settings', './routes/mypage/settings.tsx'),
- ]),
+ ...prefix('mypage', [
+ index('./routes/mypage/index.tsx'),
+ layout('./routes/mypage/layout.tsx', [
+ route('account', './routes/mypage/account.tsx'),
+ route('settings', './routes/mypage/settings.tsx'),
+ ])
+ ])
] satisfies RouteConfig
prefix を使用すると、親ルートファイルを作成する必要なく、ルートのセットにパスプレフィックスを追加できます。
ルートはネストしたいけどそれ専用のコンポーネントはいらないよというときに使えます。
...prefix('mypage', [
インデックスルートは、親の URL で親の Outlet にレンダリングされます(デフォルトの子ルートのように)。
index('./routes/mypage/index.tsx'),
ただ親ではプレフィックスを使っているためOutletではなく./routes/mypage/index.tsx
こちらのレンダリング結果がそのまま表示されます。
つまりこの設定で/mypage
にアクセスすると./routes/mypage/index.tsx
を表示するようになりました。
layout('./routes/mypage/layout.tsx', [
こちらはレイアウト専用のコンポーネントを定義できるlayout
となります。
URLのネストをさせたくないけど、子ルートのaccount.tsx
とsettings.tsx
に対して共通のレイアウトを適用したい場合に使用します。
このように設定することで親と子ルートの関係を細かく分離できるようになりました。
少し動作確認をしたいのでコンポーネントを作成、編集します。
まずはレイアウト用のコンポーネント
import { Outlet } from 'react-router'
export default function MyPage() {
return (
<>
<h1>マイページ</h1>
<Outlet />
</>
)
}
このように設定することでレイアウトが適用される子ルートに対して「マイページ」という表示がされます。
次は/mypage
にアクセスされたときに表示するコンポーネントです。
- import { Outlet } from 'react-router'
export default function MyPage() {
return (
<>
<h1>マイページトップ</h1>
- <Outlet />
</>
)
}
/mypage
にアクセスされた場合に「マイページトップ」と表示されるようになります。
動作確認
まずは/mypage
「マイページトップ」という文言が表示されています。
次に/mypage/account
するとレイアウトで設定した「マイページ」という文言と子ルートにあたる「アカウント」という文字が表示されることが確認できます。
最初のネストしたルートだと親ルートの表示内容が子ルートに直接影響を与えていましたが、今回の設定でそれを分割し独立して動かすことが可能となりました。
ナビゲーション
これまで幾つかのURLを定義してきました。
都度、ブラウザでURLを直接入力していくには厳しいボリュームになってきましたのでそれぞれのページにアクセスしやすくナビゲーションを作っていきます。
その前に現状の画面遷移を一旦整理してみます。
-
トップページ
/
→routes/home.tsx
-
投稿
/post/:postId
→routes/post.tsx
(:postId
は投稿の ID) -
マイページ
/mypage
→routes/mypage/index.tsx
-
アカウント
/mypage/account
→routes/mypage/account.tsx
-
設定画面
/mypage/settings
→routes/mypage/settings.tsx
-
このようになっていますので2種類のナビゲーションを用意します。
- トップからマイページに遷移
- マイページ内でアカウントや設定画面に遷移
トップからマイページに遷移
ヘッダー部分に表示するナビゲーション用のコンポーネントを作成します。
import { NavLink } from 'react-router'
export function HomeNavigation() {
return (
<nav className='pb-5 mb-5 border-b flex gap-4'>
<NavLink to="/" end>
トップ
</NavLink>
<NavLink to="/mypage" end>
マイページ
</NavLink>
</nav>
)
}
React RouterのNavLink
を使います。
NavLink
はアクティブ状態をレンダリングする必要があるナビゲーションリンク用です。
具体的には該当のURLにアクセスがしているときにactive
というクラスが設定されます。
そのためCSSでactive
に対して文字色を変えたり背景色を付けたりできるようになります。
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body {
padding: 20px;
}
h1 {
@apply font-bold text-xl
}
h2 {
@apply font-semibold text-lg
}
.active {
@apply text-sky-600;
}
今回はactive
クラスがついている要素に対して文字色を青にしました。
ナビゲーション用のコンポーネントが作成できましたのでトップページに適用します。
import { posts } from '~/const/posts'
import type { Route } from './+types/home'
import { Link } from 'react-router'
+ import { HomeNavigation } from '~/components/home-navigation'
export function meta({}: Route.MetaArgs) {
return [
{ title: 'New React Router App' },
{ name: 'description', content: 'Welcome to React Router!' },
]
}
export default function Home() {
return (
<>
- <h1>トップ</h1>
+ <HomeNavigation />
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link to={`/post/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
</>
)
}
ついでにマイページのトップにもナビゲーションを適用します。
+ import { HomeNavigation } from "~/components/home-navigation";
export default function MyPage() {
return (
<>
+ <HomeNavigation />
<h1>マイページトップ</h1>
</>
)
}
それではナビゲーションを使ってトップとマイページのトップにアクセスできるか確認します。
想定通りページの切り替えができ、且つ現在自分のいる場所がどこかわかるように文字色が青になりました。
マイページ内でアカウントや設定画面に遷移
次はマイページ内でアカウントや設定画面に遷移しやすくするためのナビゲーションを作成します。
import { NavLink } from 'react-router'
export function MyPageNavigation() {
return (
<nav className="mb-5 flex gap-4 border-b pb-5">
<NavLink to="/mypage/account" end>
アカウント
</NavLink>
<NavLink to="/mypage/settings" end>
設定画面
</NavLink>
</nav>
)
}
基本的には先ほど作成したトップで使用していたナビゲーションと同じような構造になります。
リンク先だけアカウントや設定画面用を設定しています。
各画面にナビゲーションを適用していきます。
まずはマイページの子ルートのレイアウト。
import { Outlet } from 'react-router'
+ import { HomeNavigation } from '~/components/home-navigation'
+ import { MyPageNavigation } from '~/components/mypage-navigation'
export default function MyPage() {
return (
<>
+ <HomeNavigation />
+ <MyPageNavigation />
<Outlet />
</>
)
}
今回作成したマイページ用のナビゲーションにプラスしてトップへ遷移できるナビゲーションも設定しました。
これでアカウントや設定画面からトップへの遷移もできるようになります。
次はマイページのトップです。
import { HomeNavigation } from '~/components/home-navigation'
+ import { MyPageNavigation } from '~/components/mypage-navigation'
export default function MyPage() {
return (
<>
<HomeNavigation />
+ <MyPageNavigation />
<h1>マイページトップ</h1>
</>
)
}
マイページのトップからアカウントや設定画面に遷移するためにナビゲーションを設定しました。
最後にトップへ遷移するナビゲーションを修正します。
import { NavLink } from 'react-router'
export function HomeNavigation() {
return (
<nav className="mb-5 flex gap-4 border-b pb-5">
<NavLink to="/" end>
トップ
</NavLink>
- <NavLink to="/mypage" end>
+ <NavLink to="/mypage">
マイページ
</NavLink>
</nav>
)
}
end
を削除しました。
end
がついているとto
で設定したURL
にアクセスした時のみactive
クラスが設定されます。
逆に外すとto
で設定したURL
を含むもの全てのURLでactive
クラスが設定されます。
それでは動作確認を行います。
/
へのアクセスはこれまで通りとなります。
マイページをクリックするとマイページのトップに遷移しマイページ用のナビゲーションが表示されました。
設定画面をクリックすると設定画面に遷移し、「マイページ」「遷移画面」2つの文字色が青になりました。
これで現在自分のいる場所がマイページの設定画面だということがわかります。(実際のUIはアイコンや表示サイズを変えるなどしてもう少しわかりやすくした方がいいかと思いますが)
ここから「トップ」をクリックするとトップページに遷移もできます。
忘れていましたがブログの個別記事を表示するコンポーネントにもナビゲーションを入れておきます。
import { getPostById } from '~/const/posts'
import type { Route } from './+types/post'
+ import { HomeNavigation } from '~/components/home-navigation'
export default function Post({ params }: Route.ComponentProps) {
const post = getPostById(Number(params.postId))
if (!post) {
return (
+ <>
+ <HomeNavigation />
+ <div>投稿が見つかりませんでした。</div>
+ </>
)
}
return (
<div>
+ <HomeNavigation />
<h1>{post.title}</h1>
{post.description}
</div>
)
}
さいごに
ここまで、React Router v7のフレームワーク機能を活用したブログサイト構築のさまざまなルーティング方法をご紹介しました。今回解説した内容を押さえることで、React Router v7を用いた柔軟なルーティング設計が可能になり、実践的なスキルを身につけることができたのではないでしょうか。
アプリケーション開発は、ユーザー体験を向上させる工夫の積み重ねです。今回の内容が、あなたのプロジェクトに役立つ一歩となれば幸いです。
次のブログ記事
JSON Placeholderというサンプルのjson
を返すサーバーを用いて投稿データをサーバサイド、クライアントサイドで取得する方法をご紹介しています。
レスポンスが遅い場合は考慮してスピナーなどを処理中に表示するなども併せて実装し解説しています。
関連する技術ブログ
React Router v7(フレームワーク利用) 実践ガイド:ブログサイトを作りながら学ぶサーバーサイド、クライアントサイドのレンダリング
2025/01/23React Router v7(ライブラリ利用)を使ったブログサイトの構築ガイド
2025/01/20React開発を加速するVite入門:高速で柔軟なプロジェクトセットアップガイド
2025/01/17Mock Service Worker (MSW) を使ったAPIモックとテストの効率化
2023/09/25Next.jsとAuth.jsで認証機能を実装するチュートリアル
2024/09/13Next.jsでのメール認証処理の実装ガイド:アカウント登録からトークン検証まで
2024/05/10Next.jsでのメール認証処理の実装ガイド:トークン検証からログイン画面へのリダイレクト処理までの詳細解説
2024/05/13Next.jsを活用したGitHubとGoogleのOAuth認証実装完全ガイド — スムーズなユーザーログインの実現方法
2024/06/11