관리자 사이트를 개발하다 보면 페이지 수가 계속 증가하면서 다음과 같은 문제를 겪게 됩니다:
1. 코드 가독성 저하
페이지마다 import 문을 추가해야 하므로 코드가 복잡해집니다.
2. 초기 로딩 속도 저하
모든 페이지를 한 번에 로드하므로 번들 크기가 커지고 초기 로딩 속도가 느려집니다.
Next.js에서 폴더 구조에 따라 라우팅되는 방식을 React Router에 적용해 보았습니다.
구현 코드: Dynamic Route 컴포넌트
import React, { useCallback, useEffect, useState, createElement } from "react";
import { Navigate, useLocation } from "react-router-dom";
const AsyncComponent = (props) => {
const [Component, setComponent] = useState(null);
useEffect(() => {
let cleanedUp = false;
props.component
.then((component) => {
if (!cleanedUp) setComponent(() => component);
})
.catch((e) => {
if (!cleanedUp) setComponent(null);
throw e;
});
return () => {
setComponent(null);
cleanedUp = true;
};
}, [props.path]);
return Component ? createElement(Component) : props.loading;
};
export const DynamicRoute = (props) => {
const location = useLocation();
const getPage = useCallback(
(path) =>
import("../pages" + path)
.then((module) => module.default)
.catch((e) => {
if (/not find module/.test(e.message)) {
return import("../pages/missing").then((module) => module.default);
}
throw e;
}),
[]
);
if (!(props.auth ?? true)) {
return <Navigate replace to={props.redirect ?? "/"} />;
}
return (
<AsyncComponent
path={location.pathname}
component={getPage(location.pathname)}
loading={<LoadingPage />}
/>
);
};
const LoadingPage = () => <>loading...</>;
Dynamic Route를 활용하여 다음과 같은 결과를 얻었습니다:
1. 폴더 구조 기반의 라우팅
Next.js처럼 pages 폴더 구조에 따라 라우팅을 자동으로 처리합니다.
2. 코드 분할 및 Lazy Loading
필요한 컴포넌트만 로드하므로 번들 크기를 줄이고 초기 로딩 속도를 개선합니다.
3. 유지 보수성 향상
import 문 관리가 필요 없어져 코드가 간결해졌습니다.
폴더 구조와 사이드바 구성

라우팅 동작

코드 분할 (Code Splitting)
필요한 컴포넌트만 로드하여 번들 크기를 줄이고 초기 네트워크 비용을 감소시킵니다.
Lazy Loading
사용자가 특정 페이지를 방문할 때만 로드하여 초기 리소스 로드를 최적화합니다.
지각된 성능(perceived performance) 향상
로딩 중에 사용자에게 Loading... 화면을 보여줌으로써 빠르게 반응하는 애플리케이션이라는 인상을 줍니다.
import React, { Suspense, lazy } from "react";
import { Navigate, useLocation } from "react-router-dom";
const LoadingPage = () => <>loading...</>;
const DynamicRoute = (props) => {
const location = useLocation();
const getPage = (path) =>
lazy(() =>
import("../pages" + path).catch((e) => {
if (/not find module/.test(e.message)) {
return import("../pages/missing");
}
throw e;
})
);
if (!(props.auth ?? true)) {
return <Navigate replace to={props.redirect ?? "/"} />;
}
const Component = getPage(location.pathname);
return (
<Suspense fallback={<LoadingPage />}>
<Component />
</Suspense>
);
};