Skip to content

code splitting을 위해 router 구조 변경하기

keeep-runnning edited this page Mar 15, 2023 · 4 revisions

기존 코드의 문제점

...
const BlogLayout = lazy(() => import("./pages/blogs/BlogLayout"));
const BlogOwnerPostList = lazy(() => import("./pages/blogs/BlogOwnerPostList"));
const BlogOwnerIntroduction = lazy(() => import("./pages/blogs/BlogOwnerIntroduction"));

export default function App() {
  return (
    <Routes>
      <Route path="/" element={<RootLayout />}>
        <Route index element={<Home />} />
        ...
        <Route 
          path="blogs/:username" 
          element={
            <Suspense fallback={<LazyPageLoadingMessage />}>
              <BlogLayout />
            </Suspense>
          }
        >
          <Route index element={<BlogOwnerPostList />} />
          <Route
            path="introduction"
            element={<BlogOwnerIntroduction />}
          />
        </Route>
        ...
      </Route>
      <Route path="*" element={<NotFound />} />
    </Routes>
  );
}
  • react router의 nested routes를 사용 중이다.

  • "blogs/:username"의(BlogLayout 컴포넌트) child route로 index와(BlogOwnerPostList 컴포넌트) "introduction"을(BlogOwnerIntroduction 컴포넌트) 두었다.

  • Route-based code splitting을 위해 lazy 함수를 호출해 BlogLayout, BlogOwnerPostList, BlogOwnerIntroduction을 lazy-loaded React component로 만들었다. 따라서 이 컴포넌트들이 필요할 때, 컴포넌트 코드를 클라이언트로 가져온다.

  • 유저가 blogs/test-user에 있다가 처음으로 blogs/test-user/introduction으로 이동하면 BlogOwnerIntroduction 컴포넌트 코드가 없기 때문에 BlogLayout을 감싸는 Suspense에 정해준 fallback이 rendering 된다.

    • BlogOwnerIntroduction과 가장 가까운 Suspense는 BlogLayout을 감싸는 Suspense이다.
    • BlogOwnerIntroduction 코드를 가져오는 동안, BlogLayout 위치에 fallback이 나타나서 사용자 경험상 좋지 않다.
    • 이를 해결하기 위해 child route의 컴포넌트인 BlogOwnerPostList, BlogOwnerIntroduction를 감싸는 Suspense를 별도로 둘 수 있다.
    • child route의 컴포넌트 각각을 Suspense로 감싸는 것도 해결방안이 될 수 있지만, 사용자가 이미 blogs/* 위치에 있다면, blogs 하위에 있는 모든 컴포넌트를 한 번에 가져오는 것이 더 좋을 것 같다.
    • 이를 위해서 router의 구조를 살짝 변경해주어야 한다.

router 구조 변경

react router 문서에서 단일 route 이외에, route의 묶음도 lazy loading 하는 방법을 소개하고 있다.

  1. blogs 관련 route들을 별도의 Routes 컴포넌트로 묶는다. 즉, blogs 관련 route를 분리한다.

    export default function Blogs() {
      return (
        <Routes>
          <Route path="/" element={<BlogLayout />}>
            <Route index element={<BlogOwnerPostList />} />
            <Route
              path="introduction"
              element={<BlogOwnerIntroduction />}
            />
          </Route>
          <Route path="*" element={<NotFound />} />
        </Routes>
      );
    }
  2. 전체적인 라우팅을 하는 App 컴포넌트에서 lazy-loaded React component로 선언된 Blogs 컴포넌트를 blogs/:username/*와 연결하면 된다. blogs/:username이 아니라 꼭 blogs/:username/*에 연결해야 한다. blogs/:username에 연결하면 제대로 라우팅 되지 않는다. 일반 컴포넌트를(예를 들면, Home 컴포넌트) 라우트로 등록하는 것과 Routes를(Blogs 컴포넌트) 라우트로 등록하는 것의 차이점이다.

    ...
    const Blogs = lazy(() => import("./pages/blogs/Blogs"));
    
    export default function App() {
      return (
        <Routes>
          <Route path="/" element={<RootLayout />}>
            <Route index element={<Home />} />
            ...
            <Route
              path="blogs/:username/*"
              element={
                <Suspense fallback={<LazyPageLoadingMessage />}>
                  <Blogs />
                </Suspense>
              }
            />
            ...
          </Route>
          <Route path="*" element={<NotFound />} />
        </Routes>
      );
    }

결과

  • 유저가 /blogs/test-user를 처음 방문하는 순간 blogs 관련 컴포넌트 코드를 모두 가져온다.

    • 따라서 이후에 /blogs/test-user/introduction에 처음 방문해도 loading indicator가 표시되지 않는다. 이미 관련 컴포넌트의 코드가 클라이언트에 있기 때문이다.
  • App 컴포넌트가 깔끔해졌다.

    • blogs 관련 라우팅을 Blogs 컴포넌트가 처리하기 때문이다.