💨

React Router (react-router-dom) v6でのリダイレクト

2022/08/25に公開

ログイン状態に応じて以下のようなページリダイレクトを行いたいことがあるかと思います。

  • 未ログイン時に自動でログインページに移動
  • 既にログインしている場合、トップページに移動

このリダイレクトのやり方がreact-router-domのv6(執筆時点最新版)にて変更になっていますのでまとめてみます。

推奨される方法

まず、react-router-domではv6にてブラウザ側でのリダイレクトが非推奨となりました。

https://gist.github.com/mjackson/b5748add2795ce7448a366ae8f8ae3bb

詳しい説明は上の記事に譲りますが、以下のような問題点が指摘されています。

  • 本来リダイレクトで去るはずのページのHTMLをクライアントが一旦受信してしまう
    • 特にクローラ等がこれを受け取るとリダイレクト元のページもインデックスされてしまう
  • ブラウザ側でリダイレクトすると、本来不要なページのレンダリングが挟まるため余計な処理時間がかかる

このため、可能な限りサーバー側でリダイレクトのレスポンスを直接返すことが推奨されています。
即ちHTTP 301/302等のレスポンスをサーバー側から返すようにするということです。

例えばNext.jsを使っている場合は以下のようにgetServerSideProps内でredirectを生成する等の方法で対処します。

export async function getServerSideProps(context) {
  if (!isLoggedIn) { // 何らかのログイン判定処理
    return {
      redirect: {
        permanent: false,
	destination: '/login',
      },
    };
  }
}

ブラウザ側でのリダイレクトが必要な場合

しかし、SPAなどでHTMLをサーバー側で動的に生成しない場合など、依然としてブラウザ側でのリダイレクトが必要な場合があるかと思います。
そのような場合、<Navigate replace to="..."/>を使うことでリダイレクトすることができます。
(v5までは<Redirect to="..."/>もあったのですが廃止されています。)

ここでは、react-queryによりログイン情報を取得する場合を想定してコードを書いてみます。

import { useQuery } from 'react-query';
import { useLocation, Navigate } from 'react-router-dom';

export default function AppLayout() {
  // ログイン情報の取得
  const { data: isLoggedIn, isFetched } = useQuery('/api/session');
  
  // 現在のURLの確認(無限ループ防止)
  const location = useLocation();
  const isOnLoginPage = location.pathname === '/login';
  
  // ログイン情報が取得できるまでは何も表示しない
  if (!isFetched) return <></>;
  
  // 本来いるページと異なる場合はNavigateを返す
  if (!isLoggedIn && !isOnLoginPage) return <Navigate replace to="/login"/>;
  if (isLoggedIn && isOnLoginPage) return <Navigate replace to="/"/>;
  
  return <>
    ... {// 本来のアプリの中身}
  </>;
}

まとめ

  • ページリダイレクトは可能な限りサーバー側で行う
  • SPA等でブラウザ側でのリダイレクトが必要な場合は<Navigate replace to="..."/>を使う

Discussion