본문 바로가기

페이지 이동

React Router에서 제공하는 Link 컴포넌트에 대해 알아보도록 하겠습니다. Link 컴포넌트는 클릭하면 다른 경로로 이동하는 링크를 생성합니다. 이때 주소는 바뀌지만, 페이지를 새로 불러오진 않습니다.

import { createBrowserRouter, RouterProvider, Link } from 'react-router-dom';

const router = createBrowserRouter([
  {
    path: '/',
    element: <Index />,
  },
  {
    path: '/one',
    element: <One />,
  },
  {
    path: '/two',
    element: <Two />,
  },
  {
    path: '/three',
    element: <Three />,
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

function Index() {
  return (
    <div>
      <nav>
        <Link to="/">home</Link>
        <Link to="/one">one</Link>
        <Link to="/two">two</Link>
        <Link to="/three">three</Link>
      </nav>
      <h1>home</h1>
    </div>
  );
}

function One() {
  return (
    <div>
      <nav>
        <Link to="/">home</Link>
        <Link to="/one">one</Link>
        <Link to="/two">two</Link>
        <Link to="/three">three</Link>
      </nav>
      <h1>one</h1>
    </div>
  );
}

function Two() {
  return (
    <div>
      <nav>
        <Link to="/">home</Link>
        <Link to="/one">one</Link>
        <Link to="/two">two</Link>
        <Link to="/three">three</Link>
      </nav>
      <h1>two</h1>
    </div>
  );
}

function Three() {
  return (
    <div>
      <nav>
        <Link to="/">home</Link>
        <Link to="/one">one</Link>
        <Link to="/two">two</Link>
        <Link to="/three">three</Link>
      </nav>
      <h1>three</h1>
    </div>
  );
}

export default App;

Link 컴포넌트를 작성하면 화면에서 링크 요소를 볼 수 있습니다. 이는 HTML의 a 태그와 외관상 똑같아 보이지만 a 태그와 Link 컴포넌트는 동작 방식이 다릅니다.

  • a 태그: 클릭 시 href 속성에 작성된 경로로 이동하면서 동시에 페이지를 새로 불러오기 때문에 새로고침이 됩니다. 이로 인해 속도가 느려질 수 있습니다.
  • Link 컴포넌트: HTML5 History API를 사용해서 브라우저의 주소를 바꿔주기 때문에 페이지가 새로고침 되지 않습니다. 대신 필요한 DOM 요소만 변경하여 화면의 일부분만 업데이트합니다. 따라서 a 태그보다 빠른 속도와 부드러운 사용자 경험을 제공합니다.

이러한 이유로 React Router를 사용할 때는 일반적인 페이지 내부 이동에 a 태그 대신 Link 컴포넌트를 사용합니다.

HTML5 History API란?

브라우저의 세션 기록(방문 기록)을 JavaScript로 조작할 수 있게 해주는 웹 API입니다. pushState()와 replaceState() 메서드를 통해 페이지를 새로 불러오지 않고도 URL을 변경할 수 있으며, 이를 통해 SPA에서 부드러운 페이지 전환이 가능해집니다.

History API - Web APIs | MDN

1.1 to 속성

to 속성은 접근할 경로를 지정해줍니다. 이때 경로는 상대 경로 혹은 절대 경로로 지정해줄 수 있습니다.

[폴더 구조 예시]

src/
├── pages/
│ ├── Home.jsx
│ ├── About.jsx
│ └── products/
│   ├── index.jsx
│   ├── ProductDetail.jsx
│   └── Category.jsx
├── components/
│ └── Navigation.jsx
└── App.jsx
  1. 상대 경로

현재 경로를 기준으로 합니다.

// src/pages/products/ProductDetail.jsx
import { Link } from 'react-router-dom';

function ProductDetail() {
  return (
    <div>
      <h1>Product Detail</h1>
      {/* 현재 경로: /products/1 */}
      <Link to="..">Products List</Link> {/* /products로 이동 */}
      <Link to="../category">Categories</Link> {/* /products/category로 이동 */}
      <Link to=".">Current Page</Link> {/* 현재 페이지 새로고침 */}
    </div>
  );
}
  1. 절대 경로

최상위 루트(/)를 기준으로 합니다.

// src/components/Navigation.jsx
import { Link } from 'react-router-dom';

function Navigation() {
  return (
    <nav>
      <Link to="/">Home</Link>
      <Link to="/about">About</Link>
      <Link to="/products">Products</Link>
      <Link to="/products/category">Categories</Link>
    </nav>
  );
}

여기서 /about이 /pages/about이 아닌 이유는 파일 경로와 URL 경로는 다른 개념이기 때문입니다.

  • 파일 경로: 프로젝트 내 실제 파일 위치 (src/pages/About.jsx)
  • URL 경로: 브라우저 주소창에 표시되는 경로 (/about)

React Router에서 to="/about"은 파일 시스템이 아니라 라우터에 정의한 경로를 참조합니다.

const router = createBrowserRouter([
  {
    path: '/about', // ← 이 URL 경로를 참조
    element: <About />, // About.jsx 파일이 어디 있든 상관없음
  },
]);

즉, pages 폴더는 개발자가 코드를 정리하기 위한 구조일 뿐이고, 사용자에게 노출되는 URL은 라우터 설정에서 자유롭게 정의할 수 있습니다.

1.2 to 속성값 (경로 형태)

to의 속성값으로는 문자열이나 객체 형태로 작성할 수 있습니다.

// 문자열
<Link to="/courses?sort=name&sorting=asc#the-hash" />

// 객체
<Link
  to={{
    pathname: "/courses",
    search: "?sort=name&sorting=asc",
    hash: "#the-hash"
  }}
  state={{ fromDashboard: true }}
/>

객체로 넣어줄 경우 pathname, search, hash 프로퍼티의 값들을 넣어서 객체 형태로 작성합니다.

  • pathname : 이동할 경로를 나타내는 문자열입니다.
  • search : 쿼리스트링을 나타내는 문자열입니다. 위의 경우 쿼리스트링 ?sort=name&sorting=asc이 포함된 경로로 이동합니다.
  • hash : URL의 해시를 나타내는 문자열입니다. to={{ pathname: '/about', hash: '#contact' }}와 같이 객체 형태로 전달할 경우 /about#contact 경로로 이동합니다.
  • state : 이동할 경로와 함께 전달할 상태 정보입니다. URL에 보이지 않으며 useLocation 훅을 통해 접근할 수 있습니다.

쿼리스트링이란?

URL의 뒷부분에 작성하여 서버에 데이터를 전달하는 방법 중 하나로 ?key=value 형태입니다. ?sort=name&sorting=asc처럼 &를 사용하여 여러 데이터를 전달할 수 있습니다.


URL 해시(Hash)란?

URL의 # 기호 뒤에 오는 부분으로, 페이지 내 특정 위치로 이동할 때 사용합니다. 예를 들어 /about#contact로 이동하면 about 페이지의 id="contact" 요소로 스크롤됩니다. 해시 값은 서버로 전송되지 않고 브라우저에서만 처리됩니다.

1.3 search 속성과 state 속성의 차이

search 속성과 state 속성은 모두 정보를 전달하는 데 사용되지만, 그 방식과 용도에 중요한 차이가 있습니다.

  1. 가시성
    • search: URL에 직접 표시됩니다. (?key=value 형태)
    • state: URL에 표시되지 않습니다. (내부적으로 관리)
  2. 데이터 크기
    • search: URL 길이 제한으로 인해 작은 양의 데이터만 전달 가능합니다.
    • state: 비교적 큰 객체도 전달할 수 있습니다.
  3. 보안
    • search: URL에 노출되므로 민감한 정보를 포함해서는 안 됩니다.
    • state: URL에 노출되지 않아 상대적으로 더 안전합니다.
  4. SEO
    • search: 검색 엔진이 인식할 수 있어 SEO에 영향을 줄 수 있습니다.
    • state: 검색 엔진에 의해 인식되지 않습니다.
  5. 사용 사례
    • search: 필터링, 정렬, 페이지네이션 등 페이지 상태를 표현할 때 주로 사용합니다.
    • state: 페이지 간 전환 시 복잡한 객체나 일시적인 데이터를 전달할 때 사용합니다.
// search 속성 사용 예시
<Link to={{ pathname: '/products', search: '?category=electronics&sort=price' }}>
  전자제품 (가격순)
</Link>

// state 속성 사용 예시
<Link
  to="/product/123"
  state={{ fromPage: 'list', lastScrollPosition: 1500 }}
>
  제품 상세 보기
</Link>

NavLink는 Link와 비슷하지만, 현재 경로와 일치할 때 특별한 스타일이나 클래스를 적용할 수 있습니다. 주로 네비게이션 메뉴에서 현재 활성화된 링크를 표시할 때 사용합니다.

import { createBrowserRouter, RouterProvider, NavLink } from 'react-router-dom';

const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      { path: '/', element: <Home /> },
      { path: '/about', element: <About /> },
      { path: '/products', element: <Products /> },
    ],
  },
]);

function Layout() {
  return (
    <div>
      <nav>
        <NavLink
          to="/"
          className={({ isActive }) => (isActive ? 'active' : '')}
        >
          홈
        </NavLink>
        <NavLink
          to="/about"
          className={({ isActive }) => (isActive ? 'active' : '')}
        >
          소개
        </NavLink>
        <NavLink
          to="/products"
          className={({ isActive }) => (isActive ? 'active' : '')}
        >
          상품
        </NavLink>
      </nav>
      <Outlet />
    </div>
  );
}

function App() {
  return <RouterProvider router={router} />;
}

export default App;
/* CSS */
nav a {
  margin-right: 10px;
  text-decoration: none;
  color: #333;
}

nav a.active {
  color: blue;
  font-weight: bold;
  border-bottom: 2px solid blue;
}

NavLink의 className prop은 함수를 받을 수 있으며, 이 함수는 isActive와 isPending 속성을 가진 객체를 매개변수로 받습니다. isActive는 현재 URL이 해당 NavLink의 to 경로와 일치하면 true가 됩니다. 사용자가 현재 보고 있는 페이지의 링크를 강조 표시할 때 사용합니다. isPending은 해당 경로로 이동 중이지만 아직 완료되지 않았을 때 true가 됩니다. loader로 데이터를 불러오는 동안 "로딩 중" 스타일을 적용할 때 유용합니다.

<NavLink
  to="/about"
  className={({ isActive, isPending }) =>
    isPending ? 'pending' : isActive ? 'active' : ''
  }
>
  소개
</NavLink>

style prop도 같은 방식으로 사용할 수 있습니다.

<NavLink
  to="/about"
  style={({ isActive }) => ({
    fontWeight: isActive ? 'bold' : 'normal',
    color: isActive ? 'blue' : 'black',
  })}
>
  소개
</NavLink>

3. useNavigate

useNavigate 훅은 프로그래밍 방식으로 페이지를 이동할 때 사용합니다. 버튼 클릭, 폼 제출 후, 특정 조건 만족 시 등 사용자의 링크 클릭 없이 페이지를 이동해야 할 때 유용합니다.

import { useNavigate } from 'react-router-dom';

function LoginForm() {
  const navigate = useNavigate();

  const handleSubmit = async (e) => {
    e.preventDefault();
    // 로그인 로직 수행
    const success = await login();

    if (success) {
      // 로그인 성공 시 홈으로 이동
      navigate('/');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" placeholder="아이디" />
      <input type="password" placeholder="비밀번호" />
      <button type="submit">로그인</button>
    </form>
  );
}

3.1 navigate 함수의 사용법

import { useNavigate } from 'react-router-dom';

function ProductDetail() {
  const navigate = useNavigate();

  return (
    <div>
      <h1>상품 상세</h1>

      {/* 특정 경로로 이동 */}
      <button onClick={() => navigate('/products')}>상품 목록으로</button>

      {/* 한 페이지 뒤로 가기 */}
      <button onClick={() => navigate(-1)}>뒤로 가기</button>

      {/* 두 페이지 뒤로 가기 */}
      <button onClick={() => navigate(-2)}>두 페이지 뒤로</button>

      {/* 앞으로 가기 */}
      <button onClick={() => navigate(1)}>앞으로 가기</button>

      {/* state와 함께 이동 */}
      <button
        onClick={() => navigate('/cart', { state: { from: '/products/1' } })}
      >
        장바구니로 이동
      </button>

      {/* 현재 기록을 대체하며 이동 (뒤로 가기 시 이전 페이지로 돌아가지 않음) */}
      <button onClick={() => navigate('/home', { replace: true })}>
        홈으로 이동 (replace)
      </button>
    </div>
  );
}

3.2 navigate 옵션

navigate 함수의 두 번째 인자로 옵션 객체를 전달할 수 있습니다.

navigate('/path', {
  replace: true, // 현재 기록을 대체 (기본값: false)
  state: { key: 'value' }, // 전달할 상태 정보
});
  • replace: true로 설정하면 현재 히스토리 항목을 대체합니다. 사용자가 뒤로 가기를 눌러도 이전 페이지로 돌아가지 않습니다. 로그인 후 리다이렉트 시 자주 사용됩니다.
  • state: 이동할 페이지에 전달할 상태 정보입니다. URL에 표시되지 않으며 useLocation 훅으로 접근할 수 있습니다.

Link vs useNavigate

  • Link/NavLink: 사용자가 클릭하여 이동하는 일반적인 링크에 사용합니다.
  • useNavigate: 프로그래밍 방식으로 페이지를 이동해야 할 때 사용합니다. (폼 제출 후, 조건부 이동, 타이머 후 이동 등)

접근성(Accessibility) 측면에서 일반적인 네비게이션에는 Link나 NavLink를 사용하는 것이 좋습니다.

페이지 이동 - React 에센셜 | 위니버시티