낭 만 당

웹 렌더링을 향상시키는 법, lazy loading에 대하여 본문

Programming Record/Frontend

웹 렌더링을 향상시키는 법, lazy loading에 대하여

낭만알파카 2025. 7. 12. 12:13

웹 개발 중에 초기 렌더링이 너무 느리다는 판단을 했다.

이를 어떻게 해결할지 알아보다가, lazy loading에 대해 알게 되었다.

 

웹 페이지가 사용자에게 빠르게 보여지는 것은 사용자 경험(UX)의 핵심이다.

페이지가 느리게 로딩되면 사용자는 금세 떠나버리며, 이는 트래픽 손실로 이어진다.

이를 방지하기 위해 다양한 성능 최적화 기법이 사용되는데, 그 중 Lazy Loading(지연 로딩)은 이미지, 비디오, 콘텐츠 로딩 최적화에서 매우 효과적인 방식이다.

 

이 글에서는 Lazy Loading의 개념과 작동 방식, 구현 방법, 그리고 실제 적용 시 유의할 점까지 종합적으로 설명한다.


Lazy Loading은 웹 페이지가 로드될 때, 사용자가 당장 보지 않는 콘텐츠는 나중에 필요할 때 로드하는 방식이다.

 

즉, 화면에 보여질 때까지 리소스를 다운로드하지 않고 요청을 지연시킴으로써 초기 로딩 속도를 개선한다.

 

현대의 웹 페이지는 이미지, 비디오, 스크립트 등 다양한 리소스를 포함하고 있으며, 이들 중 상당수는 사용자가 처음 화면을 열었을 때는 보이지 않는다. 예를 들어, 긴 블로그 글의 하단 이미지, 쇼핑몰 상품 목록의 마지막 페이지 등은 처음부터 로딩할 필요가 없다.

 

Lazy Loading을 적용하면 다음과 같은 이점이 있다.

 

1. 초기 렌더링 속도 향상: 필수 리소스만 먼저 로딩

2. 네트워크 비용 절감: 불필요한 리소스 요청 방지

3. 브라우저 메모리 효율 향상: DOM에 필요한 요소만 삽입

4. 사용자 경험 개선: 페이지가 더 빠르고 부드럽게 느껴짐

 

 

 

Lazy Loading은 어떻게 작동하는가?

 

기본 원리는 간단하다.

 

“사용자가 콘텐츠를 보게 되는 시점에 로딩하라.”

 

이 때 뷰포트(Viewport)와 Intersection Observer API가 중요한 역할을 한다.

웹 브라우저는 사용자가 스크롤해서 특정 요소가 뷰포트에 진입했는지를 감지하고, 이때 비로소 해당 리소스를 로딩한다.

 

 

 

 

Lazy Loading의 유의사항 !!

1. SEO 문제: 검색 엔진 크롤러가 자바스크립트를 실행하지 않으면, Lazy Loading된 콘텐츠가 인식되지 않을 수 있다. SSR(Server Side Rendering)과 병행하면 해결 가능하다.

2. 이미지 placeholder: 빈 공간이 갑자기 채워지면 레이아웃 이동(CLS)이 발생할 수 있다. width, height 또는 CSS로 공간을 미리 확보해야 한다.

3. 브라우저 호환성: HTML의 loading 속성은 일부 구형 브라우저에서 미지원. 필요시 폴리필 또는 JS 구현을 병행해야 한다.

4. 사용자 접근성 고려: 비동기 로딩 콘텐츠는 스크린 리더와의 호환성도 고려해야 한다.

 

 

프로젝트 코드 예제 파일

//App.tsx

import {
  BrowserRouter as Router,
  Routes,
  Route,
  useLocation,
} from "react-router-dom";
import { lazy, Suspense } from "react";
import Header from "./components/layout/Header";
import { AnimatePresence } from "framer-motion";

// Lazy loading 적용
const HomePage = lazy(() => import("./pages/HomePage"));
const IntroPage = lazy(() => import("./pages/introPage"));
const NewsPage = lazy(() => import("@/pages/news/newsPage"));
const NewsDetailPage = lazy(() => import("@/pages/news/NewsDetailPage"));
const StockPage = lazy(() => import("@/pages/stock/stockPage"));
const StockDetailPage = lazy(() => import("@/pages/stock/StockDetailPage"));
const LoginPage = lazy(() => import("@/pages/auth/loginPage"));
const SignUpPage = lazy(() => import("@/pages/auth/signUpPage"));
const MyPageLayout = lazy(() => import("./components/layout/myPageLayout"));
const MyPageChart = lazy(() => import("./pages/myPage/chart"));
const MyPageNews = lazy(() => import("./pages/myPage/news"));
const AramSettingsPage = lazy(() => import("./pages/myPage/aramSettings"));
const TransactionPage = lazy(
  () => import("@/pages/transaction/TransactionPage")
);
const MyPage = lazy(() => import("./pages/myPage"));
const ProfilePage = lazy(() => import("./pages/myPage/profile"));

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

const queryClient = new QueryClient();

function AnimatedRoutes() {
  const location = useLocation();
  return (
    <AnimatePresence mode="wait">
      <Routes location={location} key={location.pathname}>
        <Route path="/intro" element={<IntroPage />} />
        <Route path="/" element={<HomePage />} />
        <Route path="/news" element={<NewsPage />} />
        <Route path="/news/:id" element={<NewsDetailPage />} />
        <Route path="/stock" element={<StockPage />} />
        <Route path="/stock/:code" element={<StockDetailPage />} />
        <Route path="/login" element={<LoginPage />} />
        <Route path="/signup" element={<SignUpPage />} />
        <Route path="/mypage" element={<MyPageLayout />}>
          <Route index element={<MyPage />} />
          <Route path="chart" element={<MyPageChart />} />
          <Route path="news" element={<MyPageNews />} />
          <Route path="profile" element={<ProfilePage />} />
          <Route path="aramSettings" element={<AramSettingsPage />} />
        </Route>
        <Route path="/transaction" element={<TransactionPage />} />
      </Routes>
    </AnimatePresence>
  );
}

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <Router>
        <div className="min-h-screen bg-white">
          <Header />
          <main className="pt-16">
            <Suspense fallback={<div />}>
              <AnimatedRoutes />
            </Suspense>
          </main>
        </div>
      </Router>
    </QueryClientProvider>
  );
}

export default App;

 

React.lazy()동적으로(import 시점에) 해당 컴포넌트를 불러오도록 하는 함수이다.

위 코드는 해당 페이지들이 앱 초기 로딩 시점에 즉시 불러오는 것이 아니라, 사용자가 해당 경로(/news, /stock, /mypage 등)에 접근했을 때 비로소 로딩된다.

이 방식은 초기 번들 크기를 줄이고, 페이지 렌더링 속도를 개선하는 데 효과적이다.

왜 React에서 Lazy Loading이 중요한가?

React 기반 SPA(Single Page Application)는 한 번에 모든 컴포넌트를 로딩하게 되면 초기 번들 크기가 커진다.

예를 들어, 사용자가 로그인 페이지만 보려고 해도, NewsPage, StockPage, MyPageChart까지 모두 로딩된다면 비효율적이다.

 

Lazy Loading은 사용자가 실제로 필요한 컴포넌트만 로딩함으로써 페이지 전환 시 지연 없는 사용자 경험을 제공한다.

 

Suspenselazy로 로딩 중인 컴포넌트를 감싸고, 로딩이 완료되기 전까지 보여줄 UIfallback으로 지정한다.

현재 코드에서는 <div />로 비워져 있지만, 실제 서비스에서는 아래와 같은 로딩 인디케이터나 스켈레톤 UI를 사용할 수 있다.

 

Lazy Loading의 동작 흐름

 

예를 들어 사용자가 처음 접속하여 / 경로의 HomePage에 접근한 경우, 다음과 같은 순서로 동작한다:

 

  1. App.tsx가 렌더링되며 <Suspense>가 마운트됨
  2. <AnimatedRoutes /> 내부에서 <Route path="/" element={<HomePage />} />가 선택됨
  3. HomePageReact.lazy()로 정의되어 있으므로 해당 시점에 실제로 import()가 수행됨
  4. 컴포넌트가 로드될 때까지 fallback UI가 표시됨
  5. 로딩이 완료되면 HomePage가 렌더링됨

 

반면, /stock이나 /mypage/chart와 같은 경로는 사용자가 해당 링크를 클릭할 때까지 로드되지 않음.

 

 

 

 

 

해당 코드의 보완 가능성

 

1. 로딩 인디케이더 추가

      사용자 입장에서 빈 <div />는 혼란스러울 수 있다. 적절한 로딩 UI를 제공해야 사용자 경험을 높일 수 있다.

2. 에러 바운더리 적용

      Lazy Loading 중 실패하는 경우를 대비해 ErrorBoundary를 함께 사용하는 것도 바람직하다.

3. 사전 로딩(Prefetch)
      예상되는 사용자 이동을 고려해 특정 경로는 미리 preload하는 것도 전략이 될 수 있다.

 

(코드에 더 보완이 필요하다!!)


Lazy Loading은 웹 렌더링 성능을 향상시키는 매우 효과적인 기법이다. 간단한 HTML 속성 하나로도 적용할 수 있으며, 필요에 따라 JS를 통한 정밀 제어도 가능하다.

제공한 App.tsx는 경로별 컴포넌트를 동적으로 로딩하는 구조로 잘 설계되어 있으며, 이를 통해 초기 렌더링 속도 향상, 불필요한 리소스 로딩 방지, 사용자 경험 개선을 모두 달성할 수 있다.

 

단순한 기술 적용을 넘어서, 언제 어떤 컴포넌트를 지연 로딩해야 할지 전략적으로 판단하는 능력이 중요하다.

 

특히 이미지가 많은 웹사이트, 무한 스크롤 콘텐츠, 대규모 싱글 페이지 앱(SPA)에서는 Lazy Loading을 통해 사용자 경험을 크게 향상시킬 수 있다.

 

성능이 곧 UX이며, 좋은 UX는 곧 성공적인 서비스로 이어진다. Lazy Loading은 그 출발점이 될 수 있다.