Skip to content

FE ‐ 페이지 로딩 관련 문제 및 새로운 구현 방안

vavoya edited this page Feb 24, 2025 · 2 revisions

개요

본 문서는 React 애플리케이션에서 페이지 전환 시 발생하는 로딩 처리 문제와, 사용자 경험(UX)을 개선하기 위해 도입한 새로운 구현 방안을 설명합니다.
기존의 Suspense fallback 방식은 현재 페이지가 언마운트되고 로딩 UI가 렌더링된 후 다음 페이지로 전환되는 단점을 가지고 있습니다. 이를 보완하기 위해, 기존 페이지 위에 오버레이 형태의 로딩 UI를 먼저 보여주고, 이후 React Router 자체를 lazy 로 가져와 dataStrategy 내에서 추가 로딩 처리를 진행한 후 미들웨어 및 로더를 실행하는 방식을 도입하였습니다.

구현 목표

  • 빠른 초기 로딩 UI 표시: 앱 시작 시 가장 먼저 로딩 UI를 보여주어 사용자에게 빠른 피드백을 제공.
  • React Router lazy 로딩: 라우터 컴포넌트를 lazy 로 감싸, 필요한 시점에 로딩하여 초기 번들 사이즈를 줄임.
  • dataStrategy 내 로딩 처리: dataStrategy에서 middleware 실행 및 loader 호출 전 일정 시간(1초) 후 로딩 모달을 보여줌.
  • 순차적 흐름:
    1. 로딩 UI 보여주기
    2. React Router lazy 로딩 → 라우터 가져오기
    3. dataStrategy 내에서 로딩 오버레이 표시 (1초 딜레이 후)
    4. 미들웨어 실행
    5. 각 라우트의 loader 실행
    6. 로딩 모달 제거

1초 딜레이 적용 이유

1초 딜레이를 두는 이유는 네트워크 응답이 1초 이내에 완료되면 로딩 UI가 깜박이지 않도록 하기 위함입니다.
네트워크가 빠를 경우, 로딩 오버레이가 나타나지 않아서 사용자에게 부자연스러운 깜박임 현상을 방지할 수 있습니다.
반대로, 1초 이상 응답이 지연되면 네트워크가 느리다는 신호로 판단하고, 데이터 fetching이 오래 걸릴 것이라 예상하여 로딩 UI를 표시합니다.

구현 방식

1. 초기 로딩 UI 및 React Router lazy 로딩

앱 시작 시 가장 먼저 빠른 초기 로딩 UI를 렌더링하고, React Router 컴포넌트를 lazy 로 로딩합니다.

// App.jsx
import React, { Suspense } from "react";
import ReactDOM from "react-dom/client";

// 초기 로딩 UI (빠른 피드백용)
import InitialLoading from "@/components/InitialLoading";

// React Router lazy 로딩
const Router = React.lazy(() => import("@/router/index"));

const App = () => {
  return (
    <Suspense fallback={<InitialLoading />}>
      <Router />
    </Suspense>
  );
};

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

위 코드는 앱 시작 시 빠르게 초기 로딩 UI를 보여주고, React Router를 lazy 로 로드하는 예시입니다.

2. dataStrategy 내 로딩 오버레이 처리 및 순차 흐름

dataStrategy에서는 1초 후에 로딩 모달을 띄워 사용자가 데이터 fetching 중임을 인지할 수 있게 합니다.
네트워크 응답이 1초 이내에 완료되면 로딩 오버레이가 나타나지 않아 깜박임을 방지하고, 1초 이상 지연될 경우 오버레이를 통해 진행 상황을 사용자에게 알려줍니다.

// dataStrategy.tsx
import { DataStrategyFunctionArgs, DataStrategyResult } from "react-router-dom";
import middleware from "@/route/middleware.tsx";
import LoadingModal, { mountModal } from "@/components/Loading";
import { CustomError, executeErrorHandler } from "@/route/error";

export default async function dataStrategy(
  { request, matches }: DataStrategyFunctionArgs
): Promise<Record<string, DataStrategyResult>> {
  const { render, unmountModal } = mountModal();
  
  // 1초 후에 로딩 모달 표시 (네트워크 응답이 1초보다 느리면 오버레이를 보여줌)
  const timeout = setTimeout(() => {
    render(<LoadingModal text={"페이지 불러오는 중"} />);
  }, 1000);

  try {
    // 미들웨어 실행 (예: 인증, 세션 체크)
    await middleware(request);

    // 각 라우트의 loader 실행 (데이터 fetching)
    const results = await executeLoaders(matches);

    // 데이터 fetching 완료 시 타이머 취소 및 로딩 모달 제거
    clearTimeout(timeout);
    unmountModal();
    return results;
  } catch (error) {
    clearTimeout(timeout);
    unmountModal();
    
    if (error instanceof CustomError) {
      executeErrorHandler(error.errorType, request);
    }
    return {
      default: {
        type: 'error',
        result: null
      }
    };
  }
}

async function executeLoaders(matches: DataStrategyMatch[]) {
  const matchesToLoad = matches.filter((m) => m.shouldLoad);
  const results = await Promise.all(
    matchesToLoad.map(async (match) => {
      const result = await match.resolve();
      if (result.result instanceof CustomError) {
        throw result.result;
      } else {
        return result;
      }
    })
  );
  return results.reduce((acc, result, i) => Object.assign(acc, {
    [matchesToLoad[i].route.id]: result,
  }), {});
}

위 코드는 dataStrategy에서 1초 후 로딩 모달을 띄운 후, 미들웨어와 각 loader를 실행하고 작업 완료 시 모달을 제거하는 전체 순서를 보여줍니다. (citeturn1file0)

3. 전체 순서 요약

  1. 초기 로딩 UI 보여주기: 앱 시작 시 <InitialLoading />이 빠르게 렌더링됩니다.
  2. React Router lazy 로딩: Suspense fallback을 통해 초기 로딩 UI 후에 Router 컴포넌트가 로드됩니다.
  3. dataStrategy 내 로딩 오버레이 표시: 네트워크 응답이 1초 이상 걸릴 경우, 로딩 모달이 표시되어 데이터 fetching 중임을 사용자에게 알립니다.
  4. 미들웨어 실행: 라우팅 전 인증 및 세션 검증을 위한 미들웨어가 실행됩니다.
  5. 각 라우트 loader 실행: 데이터 fetching을 위해 각 loader들이 병렬로 실행됩니다.
  6. 로딩 모달 제거: 모든 데이터가 준비되거나 에러 발생 시 타이머를 취소하고, 로딩 모달을 언마운트합니다.

장단점 및 고려 사항

장점

  • 빠른 초기 피드백: 초기 로딩 UI를 통해 사용자가 즉각적으로 앱이 작동 중임을 인지할 수 있음.
  • 자연스러운 전환: 기존 페이지 위에 오버레이 형태로 로딩 UI가 표시되어 페이지 전환이 부드럽게 연결됨.
  • 최소한의 번들 로딩: React Router를 lazy 로 감싸 초기 번들 사이즈를 줄이고, 필요한 시점에만 로드함.

단점 및 고려 사항

  • 구현 복잡성 증가: 여러 단계의 로딩 처리와 타이머 제어, lazy 로딩, dataStrategy 내 순차 처리 등이 결합되어 복잡도가 증가함.
  • 상태 및 에러 관리: 데이터 fetching 및 로딩 상태를 직접 관리해야 하므로, 에러 처리와 상태 동기화에 주의를 기울여야 함.
  • 타이밍 조정 필요: 1초 딜레이는 네트워크 환경에 따라 조정이 필요할 수 있음. 빠른 응답 시 깜박임을 방지하고, 느린 응답 시 적절한 로딩 UI를 보여주기 위한 값입니다.

결론

새로운 구현 방식은 앱 시작 시 초기 로딩 UI를 빠르게 표시하고, React Router를 lazy 로 감싸서 필요한 시점에 로드하는 전략을 통해 전체 로딩 과정을 세밀하게 제어합니다.
이를 통해 dataStrategy에서 미들웨어와 loader를 실행하는 동안 부드러운 로딩 오버레이를 제공하여, 사용자에게 단절된 느낌 없이 자연스러운 페이지 전환 경험을 제공합니다.
특히, 1초 딜레이를 통해 네트워크 응답이 빠른 경우 불필요한 로딩 UI가 나타나지 않도록 하여 깜박임 현상을 방지하는 효과를 얻을 수 있습니다.


참고 자료


이와 같이 구현하면 "로딩 보여주기 → 라우터 가져오기 → dataStrategy 로딩 보여주기 → 미들웨어 실행 → 로더 실행 → 로딩 사라지기" 순서로 부드러운 페이지 전환과 UX 개선을 달성할 수 있습니다.

Clone this wiki locally