frontend/react

[React] React.js 강좌 20. React Query와 Router 결합으로 페이지 전환 성능 최적화하기

mirabo01 2025. 11. 10. 08:56

1. 왜 라우터와 React Query를 함께 고려해야 할까

React Router는 페이지 간의 UI 전환을 담당하고,
React Query는 각 페이지의 데이터 로딩과 캐싱을 담당합니다.

이 두 가지를 잘 결합하면
페이지를 이동할 때마다 새로 데이터를 불러오지 않아도 되고,
사용자는 즉각적인 반응성을 느낄 수 있습니다.

예를 들어
대시보드 → 설정 → 통계 페이지로 이동하더라도
이미 캐시에 있는 데이터를 재활용하면 로딩 없이 전환이 가능합니다.


2. 예제 시나리오

이번 예제는 다음 구조를 가정합니다.

/dashboard
 ├── /stats
 ├── /notifications
 └── /settings

각 페이지는 서버로부터 데이터를 받아오며,
React Query를 통해 캐시 관리가 이루어집니다.


3. React Query 전역 설정

먼저 QueryClient를 생성할 때 기본 옵션을 설정합니다.

// queryClient.js
import { QueryClient } from '@tanstack/react-query';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 1,               // 실패 시 한 번 재시도
      refetchOnWindowFocus: false, // 포커스 시 자동 새로고침 비활성화
      staleTime: 1000 * 60 * 3, // 3분 동안 캐시 유지
    },
  },
});

export default queryClient;

index.js에서 이 클라이언트를 전역에 등록합니다.

import { QueryClientProvider } from '@tanstack/react-query';
import queryClient from './queryClient';
import App from './App';

export default function Root() {
  return (
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  );
}

4. 페이지별 React Query 적용

예를 들어 통계 페이지(DashboardStats.jsx)에서 서버 데이터를 가져오는 코드는 다음과 같습니다.

// DashboardStats.jsx
import { useQuery } from '@tanstack/react-query';
import api from './api';

export default function DashboardStats() {
  const { data, isLoading, isError } = useQuery({
    queryKey: ['stats'],
    queryFn: async () => {
      const res = await api.get('/dashboard/stats');
      return res.data;
    },
  });

  if (isLoading) return <p>통계 데이터를 불러오는 중입니다...</p>;
  if (isError) return <p style={{ color: 'red' }}>데이터 로딩 실패</p>;

  return (
    <div>
      <h3>대시보드 통계</h3>
      <p>총 방문자 수: {data.visitors}</p>
      <p>가입자 수: {data.users}</p>
      <p>활성 세션 수: {data.sessions}</p>
    </div>
  );
}

React Query는 ['stats']라는 키로 데이터를 캐싱합니다.
다시 /dashboard/stats로 돌아와도 API를 재호출하지 않습니다.


5. 페이지 이동 시 캐시 활용

React Router를 통해 다른 페이지로 이동할 때,
기존의 데이터가 즉시 표시되도록 캐시를 활용합니다.

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

function Navbar() {
  return (
    <nav>
      <Link to="/dashboard/stats">통계</Link> |{" "}
      <Link to="/dashboard/notifications">알림</Link> |{" "}
      <Link to="/dashboard/settings">설정</Link>
    </nav>
  );
}

사용자가 한 번이라도 페이지를 방문했다면,
React Query가 자동으로 캐시 데이터를 보여주고, 백그라운드에서 새 데이터를 가져옵니다.

이 과정을 Stale-While-Revalidate(SWR) 전략이라고 부릅니다.


6. 페이지 사전 로드 (Prefetch)

페이지 전환 시 로딩 없이 즉시 데이터를 표시하려면,
React Query의 prefetchQuery 기능을 사용할 수 있습니다.

예를 들어 사용자가 링크 위에 마우스를 올렸을 때 미리 데이터를 불러옵니다.

import { useQueryClient } from '@tanstack/react-query';
import { Link } from 'react-router-dom';
import api from './api';

function Navbar() {
  const queryClient = useQueryClient();

  const prefetchStats = async () => {
    await queryClient.prefetchQuery({
      queryKey: ['stats'],
      queryFn: async () => {
        const res = await api.get('/dashboard/stats');
        return res.data;
      },
    });
  };

  return (
    <nav>
      <Link to="/dashboard/stats" onMouseEnter={prefetchStats}>통계</Link>
      {" | "}
      <Link to="/dashboard/notifications">알림</Link>
      {" | "}
      <Link to="/dashboard/settings">설정</Link>
    </nav>
  );
}

이렇게 하면 사용자가 “통계” 페이지로 이동할 때
데이터가 이미 캐시에 있어 즉시 렌더링됩니다.


7. 페이지 이동 간 로딩 최적화

라우터 전환 중에도 자연스러운 UX를 유지하기 위해
React Router의 useNavigation() 훅을 사용할 수 있습니다.

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

function LoadingIndicator() {
  const navigation = useNavigation();
  const isNavigating = navigation.state !== 'idle';

  return isNavigating ? <p>페이지 이동 중...</p> : null;
}

이 컴포넌트를 App.js 상단에 두면,
라우트 전환 시 로딩 인디케이터가 표시됩니다.


8. 캐시 무효화 (Invalidate)

만약 설정 페이지에서 데이터를 수정했다면,
다음과 같이 캐시를 무효화하여 대시보드 데이터를 갱신할 수 있습니다.

import { useMutation, useQueryClient } from '@tanstack/react-query';
import api from './api';

function Settings() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: (newSettings) => api.post('/settings', newSettings),
    onSuccess: () => {
      queryClient.invalidateQueries(['stats']); // 통계 데이터 갱신
    },
  });

  return (
    <form onSubmit={(e) => { e.preventDefault(); mutation.mutate({ theme: 'dark' }); }}>
      <button type="submit">저장</button>
    </form>
  );
}

이렇게 하면 설정을 변경한 직후,
다음에 대시보드 페이지로 돌아가면 최신 데이터가 자동으로 반영됩니다.


9. 전체 구조 요약

구성요소 역할 설명

React Router 페이지 전환 URL 기반 화면 분기
React Query 데이터 관리 API 요청, 캐싱, 자동 리페칭
PrefetchQuery 사전 로딩 페이지 이동 전에 데이터 미리 요청
InvalidateQueries 갱신 관리 변경된 데이터 자동 업데이트
StaleTime 캐시 정책 일정 시간 동안 네트워크 요청 최소화

10. 마무리

이번 강의에서는 React Router와 React Query를 결합하여
페이지 전환 시 즉각적인 반응성을 구현하는 방법을 배웠습니다.

핵심 포인트:

  • 페이지 간 이동 시 React Query 캐시를 활용하면 API 호출 최소화
  • prefetchQuery()로 미리 데이터를 가져와 UX 개선
  • invalidateQueries()로 데이터 갱신 제어
  • staleTime을 조정해 캐시 유지 시간 관리

이제 여러분의 애플리케이션은 페이지 전환 시에도
끊김 없는 자연스러운 사용자 경험을 제공할 수 있습니다.

다음 강의에서는 React 애플리케이션의 전역 에러 처리와 인터셉터 기반 인증 흐름 관리를 다룰 예정입니다.