React Router における共通レイアウトの扱いについて

※この記事は「エムティーアイ Blog Summer 2025」の 8/20 分の記事です。

はじめに

こんにちは。スマートコンテンツ事業部の高木です。

今回は、私が最近知って驚いた「React Router における共通レイアウトの扱いについて」についてご紹介します。

共通レイアウトとは?

フロントエンド開発では、headerやfooterなど、どの画面でも共通して表示される部分(共通レイアウト)がありますよね。

私はこれまでReactでUIを実装する際、headerやfooterなどの共通レイアウトをApp.jsに直接インポートしていました。

そうするとルーティングの記述とレイアウトの記述が混在してしまい、コードが読みづらくなるという問題がありました。

そこで、Next.jsの機能にあるsrc/app/layout.tsxのような仕組みが実現できないか調べてみたところ、 Reactでルーティングを実装するために利用していた、React RouterのOutletというコンポーネントで共通レイアウトを扱えることを知りました。

React RouterのOutlet

reactrouter.com

ドキュメントを見てみると、

Renders the matching child route of a parent route or nothing if no child route matches.

日本語訳:親ルートの中でマッチする子ルートを表示します。マッチする子ルートがない場合は何も表示しません。

マッチする子ルートのイメージがつかなかったので、実際にサンプルコードを使って試してみます。

SomeParent.js

import { Outlet } from "react-router-dom";

export default function SomeParent() {
  return (
    <div>
      <h1>Parent Content</h1>
      <Outlet />
    </div>
  );
}

Child.js

export default function Child() {
  return <div>Child Content</div>;
}

App.js

import { BrowserRouter, Routes, Route } from "react-router-dom";
import SomeParent from "./SomeParent";
import Child from "./Child";

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route element={<SomeParent />}>
          <Route path="/child" element={<Child />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

実際に動かしてみると、

  • / にアクセスすると、親ルート(SomeParent.js)の内容のみが表示されています。

  • /child にアクセスすると、親ルート(SomeParent.js)の内容に加えて、子ルート(Child.js)の内容も表示されています。

つまり・・・

親ルートの中でマッチする子ルート=ルーティングで階層構造になっている部分を指すようです。

共通レイアウトを実装してみる

それでは実際に、以下のような共通レイアウトを作ってみます。

App.js

import { BrowserRouter, Routes, Route } from "react-router-dom";
import SomeParent from "./SomeParent";
import Layout from "./Layout";

export default function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route element={<Layout />}>
          <Route path="/" element={<SomeParent />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

Layout.js

import Header from "./Header";
import Sidebar from "./Sidebar";
import { Outlet } from "react-router-dom";
import "./layout.css";

const Layout = () => {
  return (
    <div className="layout-root">
      <Header />
      <div className="layout-content">
        <Sidebar />
        <main className="main">
          <Outlet />
        </main>
      </div>
    </div>
  );
};

export default Layout;

実際に動かしてみると、

App.jsで記述したルーティングにより、Layoutの下の階層にあるページ全てに共通レイアウトが適用されます。

Outletを使ってみて感じたこと

メリット

  • App.jsがルーティングの記述だけになり、コードがすっきりして見やすくなる

デメリット

  • 特になし(現時点では特にデメリットは感じていません)

感想

これまではApp.jsに共通レイアウトを直接書いていたため、コードが煩雑になりがちでした。 Outletを使うことで、レイアウトとルーティングを分離でき、コードが読みやすい良い構成にできると感じました。

今後も積極的にOutletを活用していきたいと思います!