O hirunewani blog

React Router V6でOutletを利用したページコンテンツの遅延読み込み

Created at

リリースされたばかりのReact Router V6のOutletを使って、ページコンポーネントを遅延読み込みするいい感じの方法を考えた。

ページコンポーネントを雑に遅延読み込みさせていると、各ページの初回読み込み時にチラツキが発生してしまう場合がある。

またSuspenseを利用してLoading状態を作るにしても、レイアウトを構成するヘッダーなども一緒に含まれてしまうと、ユーザーはアプリが壊れたような印象を受けてしまう。

そこで、React Router v6のOutletを利用して、ページのコンテンツのみが遅延読み込みされるように、ページのコンテンツとレイアウトを分離して次のようなコードを書いた。

const listPagePreloading = import("../pages/list");
const ListPage = React.lazy(() => listPagePreloading);

const itemPagePreloading = import("../pages/items");
const ItemPage = React.lazy(() => itemPagePreloading);

const editItemPagePreloading = import("../pages/items/edit");
const EditItemPage = React.lazy(() => editItemPagePreloading);

const protectedRoutes: RouteObject[] = [
	{
		path: "/",
		element: <RequireAuth><Outlet /></RequireAuth>,
		children: [
			{
				index: true,
				element: <Navigate to="/list" replace />
			},
			{
				path: "/list",
				element: <ListPageLayout />
				children: [
					{
						index: true,
						element: <ListPage />
					}
				]
			},
			{
				path: "/items/:itemId",
				element: <ItemPageLayout />,
				children: [
					{
						index: true,
						element: <ItemPage />
					},
					{
						path: "/edit",
						element: <EditItemPage />
					}
				]
			}
		]
	}
]

ディレクトリ構造は次のようにして、レイアウトとページコンテンツはファイルを分離している。

- pages/
	- list/
		- layout.tsx
		- index.tsx
	- items/
		- index.tsx
		- layout.tsx
		- edit/
			- index.tsx
	...

RequireAuthの実装は次のようになっている。

const RequireAuth = ({ children }: { children: JSX.Element }) => {
  const auth = useAuth();
  const location = useLocation();
  if (!auth.authenticated) {
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  return children;
};