Logo

React Router로 라우팅 하기

React를 이용해서 SPA(Single Page Application)을 개발할 때 흔히 겪는 어려움 중에 하나가 웹페이지 라우팅입니다. 이번 포스팅에서는 React Router라는 라이브러리를 사용해서 SPA에서 라우팅을 하는 방법에 대해서 알아보겠습니다.

간단한 라우팅 구현

기본적으로 SPA는 index.html 파일에 div 엘리먼트만 하나 두고, 자바스크립트로 모든 부분을 동적으로 랜더링하는 구조를 취합니다. 그리고 화면의 전체 또는 일부를 브라우저에서 발생하는 이벤트에 따라서 동적으로 갱신할 수 있기 때문에 인터랙티브 한 사용자 경험을 제공할 수 있습니다.

따라서 별도의 라이브러리 없이 React만으로 간단하게 라우팅을 구현하는 것은 그리 어렵지 않은 일입니다. 예를 들어, 다음 코드 처럼 현재 어떤 컴포넌트가 랜더링되야 하는지 상태로 관리할 수 있습니다. <header> 내의 특정 메뉴 버튼을 클릭하면 상응하는 컴포넌트로 comp 상태가 바뀌기 때문에 화면에서 <main> 부분이 갱신되게 됩니다.

import React, { useState } from "react";
import Home from "./Home";
import About from "./About";
import NotFound from "./NotFound";

function App() {
  const [comp, setComp] = useState(Home);

  return (
    <>
      <header>
        <button onClick={() => setComp(Home)}>Home</button>
        <button onClick={() => setComp(About)}>About</button>
        <button onClick={() => setComp(NotFound)}>Users</button>
      </header>
      <hr />
      <main children={comp} />
    </>
  );
}

하지만 이런 방식으로 라우팅을 구현하게 되면 브라우저 사용자 입장에서 다음과 같은 문제가 발생하게 됩니다.

  • 특정 페이지에 대한 즐겨찾기 등록이 불가합니다. 컴포넌트가 전환되더라도 브라우저 주소창의 URL은 고정되어 있기 때문입니다.
  • 뒤로 가기 버튼을 누르면 해당 앱내에서 이전 페이지로 이동하는 것이 아니라 그 전에 서핑하던 다른 웹사이트로 이동해버립니다.
  • 새로 고침 버튼을 누르면 사용 중이던 컴포넌트가 아닌 무조건 최초에 렌더링되었던 Home 컴포넌트로 이동합니다.

또한 SEO(검색 엔진 최적화) 측면에서도 일반 웹사이트들과 차이가 있어서 검색 엔진에 의해 원치않는 방식으로 색인이 될 수도 있습니다.

React Router란?

React Router는 위에서 살펴본 SPA의 라우팅 문제를 해결하기 위해서 거의 표준처럼 사용되고 있는 네비게이션 라이브러리입니다. React Router를 사용하면 앱에서 발생하는 라우팅이 locationhistory와 같은 브라우저 내장 API와 완벽하게 연동이 됩니다. 따라서 SPA에서 제공하는 다이나믹한 사용자 경험을 그대로 살리면서도 기존 웹사이트에서 가능하던 브라우저 상의 매끈한 라우팅을 제공할 수 있습니다.

React Router 설치

React Router는 Web 용과 Native 용이 존재합니다. 아래와 같이 Web 용 react-router-dom을 React 애플리케이션 프로젝트에 설치해줍니다.

$ npm i react-router-dom

React Router 핵심 컴포넌트

React Router를 이해하는데 핵심이 되는 3가지 컴포넌트 대해서 먼저 짚고 넘어가가겠습니다.

HTML의 <a> 태그와 유사한 기능을 하는 컴포넌트라고 생각하시면 이해가 쉽습니다. <a> 태그는 href 속성을 통해 이동할 경로를 지정하는 반면에 <Link> 컴포넌트는 to prop을 통해서 이동할 경로를 지정해줍니다.

<Link to="/about">About</Link>

예를 들어, 위의 코드는 브라우저에서 클릭이 가능한 About으로 랜더링되고, About를 클릭하면 주소창의 경로가 <도메인 네임>/about으로 갱신됩니다. 일반적으로 화면 상단이나 좌측에 위치한 네비게이션 바를 구현할 때 주로 사용하게되는 컴포넌트입니다.

Route 컴포넌트

<Route> 컴포넌트는 현재 주소창의 경로와 매치될 경우 보여줄 컴포넌트를 지정하는데 사용됩니다. path prop을 통해서 매치시킬 경로를 지정하고 component prop을 통해서 매치되었을 때 보여줄 컴포넌트를 할당합니다.

<Route path="/about" component={About} />

예를 들어, 위의 코드는 현재 주소창의 경로가 /about일 경우 About라는 컴포넌트를 보여줍니다. 일반적으로 현재 주소창의 URL 경로에 따라 특정 컨텐츠를 보여주거나 숨기기 위해서 사용될 수 있습니다.

Router 컴포넌트

<Router> 컴포넌트는 위에 나온 <Route><Link> 컴포넌트가 함께 유기적으로 동작하도록 묶어주는데 사용합니다. 다시 말해, <Route><Link> 컴포넌트는 DOM 트리 상에서 항상 <Router>를 공통 상위 컴포넌트로 가져야합니다.

<Router>
  ...
  <Link />
  <Link />
  ...
  <Route />
  <Route />
  ...
</Router>

즉, 전체적으로 React Router를 사용하는 애플리케이션은 이와 같은 구조를 가지게 됩니다. 실제 프로젝트에서는 위 컴포넌트들이 여러 파일에 걸쳐서 흩어져 있을 수도 있겠지만, 이 큰 그림을 염두해두고 코드를 읽다보면 어렵지 않게 라우팅 흐름을 파악하실 수 있으실 것입니다.

React Router로 라우팅 구현

자 그럼, 처음에 직접 구현했던 라우팅을 React Router를 이용해서 재구현 해보겠습니다. 먼저 설치한 react-router-dom 패키지로 부터 3가지 핵심 컴포넌트를 임포트해야합니다.

import { Link, Route, BrowserRouter as Router } from "react-router-dom";

사실 React Router가 제공하는 <Router> 컴포넌트가 여러 종류가 있는데, 여기서는 대게 일반적은 라우팅을 위해 사용되는 <BrowserRouter>를 사용하였습니다. 다른 라우터들은 추후 기회가 되면 다른 포스팅를 통해서 다루도록 하겠습니다.

먼저, 헤더 부분을 <Link> 컴포넌트를 사용해서 리팩토링 합니다. to prop에 해당 메뉴 클릭 시 이동해야할 경로를 지정합니다.

<header>
  <Link to="/">
    <button>Home</button>
  </Link>
  <Link to="/about">
    <button>About</button>
  </Link>
  <Link to="/users">
    <button>Users</button>
  </Link>
</header>

다음, 메인 부분을 <Route> 컴포넌트를 사용해서 리팩토링 합니다. path prop에 매치 시 비교될 경로를 지정하고, component prop에 매치 시 보여줄 컴포넌트를 할당합니다.

여기서 / 경로를 사용하는 <Route> 컴포넌트에만 exact prop이 사용된 이유는, React Router의 디폴트 매칭 규칙 때문입니다. React Router는 path prop의 경로와 현재 브라우저의 주소창의 URL 경로 (location.pathname)와 비교를 하는데요.

현재 URL 경로 값이 <Route>path porp 값과 전체가 아닌 앞부분만 일치해도 매치되는 것으로 간주합니다. 따라서 path/일 경우, / 뿐만 아니라 /로 시작하는 모든 URL 경로, 사실 상 가능한 모든 경우의 수의 경로와 매치가 됩니다.

그렇기 때문에, exact prop이 없으면, 의도치 않게 Home 컴포넌트가 URL 경로와 상관없이 항상 보여지게 됩니다. 하지만 exact prop을 붙여주면 URL 경로 값이 <Route>path 값과 완벽히 전체가 일치해야 매치되는 것으로 처리를 해줍니다.

<main>
  <Route exact path="/" component={Home} />
  <Route path="/about" component={About} />
  <Route path="/users" component={NotFound} />
</main>

마지막으로 <Router> 컴포넌트로 위에서 작성한 모든 <Link> 컴포넌트와 <Route> 컴포넌트를 함께 감싸주기만 하면 끝입니다.

import React from "react";
import { Link, Route, BrowserRouter as Router } from "react-router-dom";
import Home from "./Home";
import About from "./About";
import NotFound from "./NotFound";

function App() {
  return (
    <Router>
      <header>
        <Link to="/">
          <button>Home</button>
        </Link>
        <Link to="/about">
          <button>About</button>
        </Link>
        <Link to="/users">
          <button>Users</button>
        </Link>
      </header>
      <hr />
      <main>
        <Route exact path="/" component={Home} />
        <Route path="/about" component={About} />
        <Route path="/users" component={NotFound} />
      </main>
    </Router>
  );
}

이제 브라우저에서 네이게이션 메뉴를 클릭해보면 브라우저의 주소가 현재 페이지에 맞게 갱신이 되는 것을 확인하실 수 있을 겁니다. 뿐만 아니라, 뒤로 가기, 앞으로 가기, 새로 고침 버튼들도 일반 웹사이트를 서핑하듯이 작동하는 것을 확인하실 수 있을 겁니다.

[보너스] 404 페이지 처리

SPA를 개발할 때도 많은 경우, 브라우저에 잘못된 경로가 입력되었을 때, 특정한 404 페이지를 보여줘야야 합니다. 이럴 경우, React Router에서 제공하는 또 다른 컴포넌트인 <Switch>로 모든 <Route> 컴포넌트로 묶어줘야 합니다. <Switch> 컴포넌트를 사용하면 그 하위에 있는 <Route> 컴포넌트 중에 매치되는 제일 첫번째 컴포넌트만 보여주고, 그 이후에 나오는 Route 컴포넌트는 매치되더라도 무시됩니다. (따라서 <Route> 컴포넌트의 순서가 중요해지겠지요?) 그 다음에 path prop이 없는 <Route> 컴포넌트를 하나 추가해주면, 이 <Route>는 모든 경로에 매치가 가능해지고, 여기에 404 컴포넌트를 할당해줄 수 있습니다. 그러면, 자연스럽게 위에 나온 <Route> 중에 매치되는 것이 없었을 경우, 제일 아래까지 내려올 것이고, 이 마지막 <Route> 컴포넌트가 매치되어 404 페이지가 보여질 것입니다.

<main>
  <Switch>
    <Route exact path="/" component={Home} />
    <Route path="/about" component={About} />
    <Route component={NotFound} />
  </Switch>
</main>

관련 포스팅