React로 Protected Route 구성하기

프론트엔드 퍼즐러·2024년 1월 8일

오늘은 react-route v6 을 사용하면서 리액트로 페이지를 구성하는 도중 로그인을 해야지만 접근 가능한 페이지와 로그인을 하지 않아도 접근 가능한 페이지를 어떻게하면 효율적으로 만들 수 있을지에 대한 여러가지 정보를 구글링 해보고 제일 효율적인 정보를 포스트해 보도록 하겠습니다.

💡 이 글은 reactreact-router v6에 대한 기본적인 지식이 있어야 이해하기 쉽습니다.

이 포스트는 code sandbox 에서 확인하실 수 있습니다. 포스트 제일 아래 공유해놓았으니 확인하시면 감사하겠습니다.

Protected Route 란?

Protected route(보호된 경로)는 주로 웹 애플리케이션에서 사용자가 로그인한 상태인지 확인하고, 로그인하지 않은 경우에는 특정 페이지에 접근하는 것을 방지하는 데 사용되는 개념입니다. 이는 일반적으로 사용자의 인증 상태를 확인하여 보호되어야 하는 페이지에 접근하는 데 사용됩니다.

환경 세팅

npx create-react-app my-protected-route --template typescript
npm install react-router-dom

환경 세팅은 위의 코드처럼 간단하게 CRA로 리액트 타입스크립트 프로젝트를 생성하고 공식 문서에 나온 react-router-dom을 설치 합니다.

그리고 우리는 routes 파일을 따로 관리할 것이기 때문에 rotes 라는 파일을 생성합니다.

폴더구조

위와 같은 그림으로 폴더 구조를 작성하시면 됩니다.

기본 페이지 구성하기

기본페이지는 / 경로의 홈페이지, 로그인 했을 때 접근이 가능한 privatePage와 페이지, 로그인 없이 접근 가능한 publicPage로 간단하게 구성하겠습니다.

index.tsx

import React from "react";
import ReactDOM from "react-dom/client";

import Routes from "./routes";

const rootElement = document.getElementById("root")!;
const root = ReactDOM.createRoot(rootElement);

root.render(
  <React.StrictMode>
    <Routes />
  </React.StrictMode>
);

index.tsx 파일에는 우리가 사용할 루트들이 들어간 컴포넌트인 Routes.tsx를 불러와서 사용합니다.

Routes.tsx

import { createBrowserRouter, RouterProvider } from "react-router-dom";
import App from "./App";

const router = createBrowserRouter([
  {
    path: "/",
    element: <App />,
  },
]);

const Routes = () => {
  return <RouterProvider router={router} />;
};

export default Routes;

Routes.tsx 파일에는 우리가 사용할 페이지를 구성합니다. 위의 코드와 같이 지금은 간단하게 홈페이지를 구성할 App.tsx 컴포넌트를 구성해 놓았습니다.

App.tsx

import { Outlet, useNavigate } from "react-router-dom";
import "./styles.css";

export default function App() {
  const nav = useNavigate();

  const handleClick = (path: string) => {
    nav(path);
  };

  return (
    <div className="App">
      Home
      <div
        style={{
          display: "flex",
          gap: "8px",
          margin: "8px auto",
          justifyContent: "center",
          cursor: "pointer",
        }}
      >
        <button
          style={{
            backgroundColor: "green",
            padding: "10px 16px",
            color: "white",
            border: "none",
            borderRadius: "5px",
            cursor: "pointer",
          }}
          onClick={() => handleClick("public")}
        >
          Public Page
        </button>
        <button
          style={{
            backgroundColor: "red",
            padding: "10px 16px",
            color: "white",
            border: "none",
            borderRadius: "5px",
            cursor: "pointer",
          }}
          onClick={() => handleClick("private")}
        >
          Private Page
        </button>
      </div>
      <Outlet />
    </div>
  );
}

App.tsx 파일은 홈페이지로, public 페이지와 private 페이지를 갈 수 있는 버튼이 있고, useNavigate를 이용하여 페이지를 이동할 수 있게 구성하였습니다.

publicPageprivatePage는 여러분들이 마음대로 구성하시면 됩니다. 저는 그냥 pages 폴더에 publicPageprivatePage 컴포넌트를 만들었습니다.

demo

위와 같이 작동하면 성공입니다.

Protected Route 만들기

이제 본격적으로 Protected Route를 만들어 보겠습니다. 우리가 수정해야 할 파일은 routes.tsx 이 한개의 파일과 protectedRoutes 라는 컴포넌트파일을 새로 만들면 됩니다.

Routes.tsx

import {
  createBrowserRouter,
  RouteObject,
  RouterProvider,
} from "react-router-dom";
import App from "./App";
import PublicPage from "./pages/publicPage";
import PrivatePage from "./pages/privatePage";
import ProtectedRoute from "./protectedRoute";

const Routes = () => {
  const userInfo = false; // true or false or localstorage get or redux get
  const routes: RouteObject[] = [
    {
      path: "/",
      element: <App />,
      children: [
        {
          path: "/public",
          element: <PublicPage />,
        },
        {
          path: "/private",
          element: <ProtectedRoute userInfo={userInfo} />,
          children: [{ path: "/private", element: <PrivatePage /> }],
        },
      ],
    },
  ];

  const router = createBrowserRouter([...routes]);

  return <RouterProvider router={router} />;
};

export default Routes;

위의 코드에서 userInfo는 유저의 정보로 유저가 존재하거나 인증된 유저가 있을 경우 boolean값으로 나타냈습니다.

그리고 새로운 routes 변수는 react-route-domRouteObject 으로 router 경로를 나타냅니다. private 경로에서 새롭게 추가된 ProtectedRoute 컴포넌트 페이지를 부모로 두었고 유저 정보를 props로 전달하였습니다.

마지막으로 router 변수의 createBrowserRouter 에 템플릿 리터럴로 routes 변수를 전달하게 되는데 이렇게 한 이유는 상황에 따라 ...AllRoute, ...AfterLoginRoutes, ...BeforeLoginRoutes등 다양한 경로를 선택하여 넣을 수 있기 때문입니다.

protectedRoute.tsx

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

interface ProtectedRouteProps {
  userInfo: boolean;
}

const ProtectedRoute: React.FC<ProtectedRouteProps> = ({ userInfo }) => {

  if (!userInfo) {
    // 유저 정보가 없다면 홈으로! 혹은 로그인페이지로 가게 할 수 있음
    return <Navigate to="/" replace={true} />;
  }

  // 유저 정보가 있다면 자식 컴포넌트를 보여줌
  return <Outlet />;
};

export default ProtectedRoute;

protectedRoute.tsx 파일은 props로 전달받은 유저 정보와 react-router-dom 에서 제공하는 Navigate 컴포넌트를 활용하여 유저 정보가 있지 않을 경우 원하는 경로로 사용자를 이동시킬 수 있습니다. 만약 인증 정보를 가지고 있다면 자식 컴포넌트를 보여줌으로써 인증된 페이지를 보여줄 수 있습니다.

아래의 결과 화면을 보면 더욱 잘 이해하실 수 있습니다.

결과 화면 - userInfo를 false로 했을 때

false

결과 화면 - userInfo를 True로 했을 때

true

최종 결과 확인 - CodeSandBox

결론

react-router-dom으로 경로를 보호하는 방법은 많은 방법이 있겠지만 최대한 간결하고 유지보수하기 쉽게 작성하는 방법으로 위의 방법이 적합하다고 생각합니다.

하지만 위의 방법은 경로 보호의 한 가지 간결한 예시로 봐주셨으면 좋겠고, 프로젝트의 특정 요구사항에 따라 다양한 방식으로 구현될 수 있습니다. 프로젝트 특성에 맞게 상황에 따라 적절한 방법을 선택하고 확장해 나가시면 좋을 것 같습니다.

이상으로 글을 마치겠습니다. 모두 Happy Coding! 하시길 바랍니다.

profile
기초를 다지고 있는 개발자

0개의 댓글