LoginSignup
UfUzR64rRzYa_Question
@UfUzR64rRzYa_Question

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

ログイン状態によるアクセス制限について

Q&AClosed

解決したいこと

ログイン状態によってアクセスできるページを制限する機能を実装しています。
┣ログイン時:制限なし
┗ログアウト時:/login /signup 以外のページにアクセスしたら /loginへリダイレクト
ひととおりの動作は実装できています。
ですがログアウト中に制限のかかったページからログインページへリダイレクトする際に一旦リダイレクト前のページが表示されてからリダイレクトが実行されます。

実現したいこと

リダイレクト前のページを見せることなくリダイレクトさせるにはどうしたらいいでしょうか

該当するソースコード

"use client";

import { useRouter, usePathname } from "next/navigation";
import { useAuthContext } from "../context/AuthContext";
import { useEffect, useState } from "react";

const PrivateRoute = ({ children, allowedRouters = [] }) => {
  const router = useRouter();
  const { user } = useAuthContext();
  const pathname = usePathname();

  useEffect(() => {
    if (!user && !allowedRouters.includes(pathname)) {
      router.push("/login");
    }
  }, [user, pathname]);

  return <>{children}</>;
};

export default PrivateRoute;

自分で試したこと

以下のようにloadingという変数で状態管理をしてみたのですが、これが動く前にリロード前のページが表示されてしまいました

const [loading, setLoading] = useState(true);
// 略
setLoading(false);
// 略
  if (loading) {
    return <p>loading...</p>;
  } else {
    return <>{children}</>;
  }
0

1Answer

public (ログイン不要)なページと、 protected (要ログイン) なページの表示・非表示の状態をクライアント側で扱うロジックを共通化してしまうと、体験されたように状態管理が困難になります。

「ログイン不要なページだけど、一瞬だけ何も表示されない瞬間がある」「(ご指摘のように)ページ遷移が走る瞬間にページが表示されてしまう」といったことが起こりがちです。 (一応、 useEffect を使わずコンポーネントの中に直にリダイレクト処理を書く方法もありますが、ちょっと怖いです...)


Next.js App Router の機能である Route Groups を使うと、 public / protected なページを宣言的な方法で管理できます。 (わざわざ includes で判定する必要が無くなる)

それに、条件判定のネストが浅くなって、煩雑な状態管理が不要になりますし、 React のコンポーネントの仕組みによくフィットしていると思います。

app/
├─ (public)/ ... ログイン不要なページを固める
│   ├─ login/
│   │   └─ page.tsx 
│   └─ signup/
│       └─ page.tsx 
│
└─ (protected)/ ... ログインが必要なページを固める
    ├─ about/
    │   └─ page.tsx 
    ├─ privacy-policy/
    │   └─ page.tsx 
    └─ layout.tsx ... 未ログイン時に login ページに飛ばす処理をここに書く
1

Comments

  1. すみません、肝心のコードを忘れてました

    app/(protected)/layout.tsx
    "use client";
    
    // 省略
    
    export const Layout = ({ children }) => {
      const router = useRouter();
      const { user } = useAuthContext();
    
      const [status, setStatus] = useState("pending");
    
      useEffect(() => {
        if (!user) {
          router.push("/login");
          setStatus("unauthenticated");
        } else {
          setStatus("authenticated");
        }
      }, [user, router]);
    
      if (status !== "authenticated") {
        return null;
      }
    
      return <>{children}</>
    }
    
  2. ご連絡が遅くなり申し訳ありません!ご回答ありがとうございます。
    あれから試行錯誤して質問時に提示したコードを元になんとか修正ができました。
    ですが、@honey32さんが記載していただいた方法もあるということで個人制作のほうではそちらをぜひ参考にさせていただきたく思います。
    ありがとうございました!

Your answer might help someone💌