전자증명서 리액트 페이지 및 레이어 동적 라우팅

전자증명서 프로젝트란 카카오톡에서 주민등록등본과 같은 증명서를 발급받고 해당 증명서를 직접 방문이 아닌 카카오톡에서 기관에 손쉽게 제출할 수 있도록 도와주는 서비스입니다.
 
프로젝트는 React로 개발하였으며 react-router를 통해 페이지, 레이어를 URL에 맞게 노출해주는 방식으로 개발이 되었습니다.
 

React Router

React Router를 간단하게 소개하자면, URL 경로를 제어하여 URL 변경 및 매개변수 사용에 도움을 주며, URL 경로에 맞는 레이아웃 구성 및 컴포넌트 렌더링을 가능하게 도와줍니다.

React Router에 대한 자세한 소개는 React Router의 getting-started tutorial을 참고하세요.

				
					// getting-started tutorial

<BrowserRouter>
  <Routes>
    <Route path="/" element={<App />} />
    <Route path="expenses" element={<Expenses />} />
    <Route path="invoices" element={<Invoices />} />
  </Routes>
</BrowserRouter>
				
			

대부분의 간단한 예제로는 위와 같이 경로에 맞게 렌더링하게 될 컴포넌트를 설정하는 형태로 작성되게 됩니다. 전자증명서에서는 각 증명서에 맞는 경로가 존재하였고, 각 경로에 맞는 페이지 컴포넌트들이 존재하였습니다.

전자증명서에서 사용된 모듈 버전은 다음과 같습니다.
react : 17.0.2
react-router : 5.2.0

 

Route 개선이 필요했던 이유

개선의 시작은 오픈 이후에 전자증명서에서 처리되고 있던 라우팅 로직의 코드가 무분별하게 증가할 것으로 예상되어 개선이 필요하다고 생각하여 시작하게 되었습니다.
 
먼저 간단한 예제코드를 보며 개선이 필요하다고 생각되었던 부분을 보겠습니다.
 
아래 App.jsx의 코드를 보시면 Route 경로에 맞게 컴포넌트들을 설정하기 위한 무수히 많은 import 구문과 JSX 로 작성된 Route를 볼 수 있을 것입니다. 
 

이 글에서 사용된 예제코드는 이해를 돕기 위한 예시 코드로 실제 사용된 코드는 아닙니다.

				
					// App.jsx

import 건강보험자격득실확인서 from './건강보험자격득실확인서';
import 코로나19예방접종증명서 from './코로나19예방접종증명서';
import 주민등록등본 from './주민등록등본';
import 주민등록초본 from './주민등록초본';
import 건강보험료납부확인서 from './건강보험료납부확인서';
import 운전경력증명서 from './운전경력증명서';
...

const App = () => {
  ...
  return (
    ...
    <Route path="/건강보험자격득실확인서">
      <건강보험자격득실확인서 />
    </Route>
    <Route path="/코로나19예방접종증명서">
      <코로나19예방접종증명서 />
    </Route>
    <Route path="/주민등록등본">
      <주민등록등본 />
    </Route>
    <Route path="/주민등록초본">
      <건강보험자격득실확인서 />
    </Route>
    <Route path="/건강보험료납부확인서">
      <건강보험료납부확인서 />
    </Route>
    <Route path="/운전경력증명서">
      <운전경력증명서 />
    </Route>
    ...
  );
};
				
			
 
전자증명서 추후 계획으로는 약 30종류 이상의 증명서 및 확인서 추가가 예정되어있는 상황이었습니다.
 
위와 같이 계속해서 추가되는 전자증명서 페이지와 레이어 수에 비례하여 코드양이 계속해서 증가할 것으로 보였고 개선이 필요하다고 생각되었습니다.
 
 
 

Route 개선

개선의 시작은 코드를 분리하는 것부터 시작하였고 페이지와 레이어 단위로 분리하여 각 경로에 맞는 Route를 설정하도록 수정하였습니다.

 
 

라우팅 정보 리스트화

페이지와 레이어를 리스트 형태로 만들어 각 경로에 맞는 컴포넌트들을 맵핑하여 분리하였고 해당 리스트를 렌더링하는 형태로 코드를 간소화하였습니다.
 
				
					// routes.jsx

import 건강보험자격득실확인서 from './건강보험자격득실확인서';
import 코로나19예방접종증명서 from './코로나19예방접종증명서';
...

const PAGE_LIST = [
  {
    path: '건강보험자격득실확인서',
    children: <건강보험자격득실확인서 />,
  },
  {
    path: '코로나19예방접종증명서',
    children: <코로나19예방접종증명서 />,
  },
  ...
];
				
			
				
					// AppRoute.jsx

const AppRoute = () => {
  ...
  return (
    <>
      {PAGE_LIST.map(({path, children}) => (
        <Route path={path}>
          {children}
        </Route>
      ))}
    </>
  );
};
				
			

위와 같은 형태로 라우팅 정보들을 리스트화하여 코드 정리를 하고 어느 정도 코드가 간소화되었지만 조금 더 코드 개선이 필요하다고 생각되었습니다.

 
추가적인 문제점으로 생각되었던 부분은 라우팅 정보가 미리 설정되어 각 경로에 맞는 컴포넌트와 관련 코드들은 미리 로드되어 초기에 로드되는 코드에 모두 포함되어 있었습니다.
 

이렇게 하나의 코드로 통합하는 것은 import를 통해 각자의 역할에 맞게 코드를 분할하여 작성할 수 있도록 도움을 주지만, 이러한 코드 통합은 코드양이 증가할수록 번들된 파일이 커지게 됩니다.

 
점점 커지게 되는 번들된 파일은 로딩 속도에 영향을 미칠 수 있으며, 이를 해결하는 방법으로는 번들된 코드를 나누는 것으로 각 역할을 맡은 코드가 필요할 때만 지연 로딩되도록 하여 개선할 수 있습니다.
 
지연 로드 방법은 React.lazy를 사용하는 방법으로 자세한 내용은 다음의 코드 분할 – React 링크에서 확인하시면 좋을 것 같습니다.
 
다음 개선은 각 경로에 맞게 해당 페이지의 컴포넌트와 관련 코드들이 로드되도록 코드 분할하여 레이지 로딩을 적용하는 형태로 개선 작업을 시작하였습니다.
 

코드 분할이란?
Javascript로 작성된 코드들은 webpack, rollup과 같은 번들러를 통해 하나의 코드로 통합한 번들된 파일을 만들어 해당 파일을 로드하여 사용할 수 있습니다. 여기서 번들된 코드를 나누어 런타임에 필요로하지 않는 코드들은 지연 로딩하여 불러올 수 있습니다.

 
 
 
라우트 기반 코드 분할
 
라우트 기반 코드 분할Route에 설정되는 컴포넌트를 React.lazy 로 로딩하여 각 라우트가 경로에 맞게 동작 시 컴포넌트를 로드하여 지연 로딩되게 구성할 수 있습니다.
 
해당 개선 방법을 적용하던 중, 라우트에 설정하기 위해 작성된 많은 페이지와 레이어들을 import 해오는 부분을 보며 해당 부분을 개선할 수는 없을까 라는 생각을 하게 되었습니다.
 
				
					import 건강보험자격득실확인서 from './건강보험자격득실확인서';
import 코로나19예방접종증명서 from './코로나19예방접종증명서';
import 주민등록등본 from './주민등록등본';
import 주민등록초본 from './주민등록초본';
import 건강보험료납부확인서 from './건강보험료납부확인서';
import 운전경력증명서 from './운전경력증명서';
...
				
			
 
생각 중에  Next.js에서 제공하는 파일시스템 기반으로 페이지 라우팅 처리 기능이 떠올랐습니다.
 
폴더별로 페이지와 레이어를 위치시키고 URL 경로에 맞게 필요한 페이지와 레이어를 동적 로딩시키도록 개선하면 어떨까 하는 생각을 하게 되었고 라우팅 기반으로 페이지와 레이어 컴포넌트를 동적 로딩하도록 작업을 진행하였습니다.

Next.js Routing
Next.js에서 제공하는 페이지 라우팅은 pages 폴더 하위 경로와 URL을 매칭시켜 URL 정보로 pages 폴더 하위 컴포넌트가 로드되도록 라우팅 처리를 별도의 코드 구성없이 지원합니다.

 

라우트 기반 동적 로딩
 
처음 개선의 시작은 각 페이지 URL에 맞는 페이지 컴포넌트를 pages 폴더를 하위에 위치 시키는 것으로 시작하였습니다.
 
 
그리고 pages 폴더 구조와 동일하게 URL 경로를 구축하는 것입니다. 도메인을 https://digitaldocs.kakao.com/ 기준으로 위의 폴더 구조에 맞게 아래처럼 설정할 수 있습니다.
 
				
					https://digitaldocs.kakao.com/건강보험료납부확인서
https://digitaldocs.kakao.com/건강보험자격득실확인서
https://digitaldocs.kakao.com/운전경력증명서
https://digitaldocs.kakao.com/주민등록등본
https://digitaldocs.kakao.com/주민등록초본
https://digitaldocs.kakao.com/코로나19예방접종증명서
				
			
기본적으로 각 폴더 하위에서는 index.jsx 기준으로 페이지를 구성하여 해당 폴더의 페이지 컴포넌트를 작성하였습니다.
 
다음으로는 Route에 설정된 경로를 받아 pages 폴더 하위에서 경로에 맞는 페이지 컴포넌트를 동적 로딩하는 코드가 필요했습니다.
 
처음 생각하였던 방식은 path에 맞게 컴포넌트를 동적 로딩해오는 동적 컴포넌트를 만들어 보는 것으로 시작하였습니다.
 
				
					import {useState, useEffect, createElement} from 'react';

const RouteComponent = ({path}) => {
  const [component, setComponent] = useState(null);

  useEffect(() => {
    const importModule = async () => {
      const {default: C} = await import(`pages/${(path)}`);
      setComponent(() => C);
    };
    importModule();
  }, [path]);

  return (
    <>{component ? createElement(component) : null}</>
  );
};

export default RouteComponent;
				
			
 
코드를 간략하게 설명하자면 location 정보에서 path 정보를 받아 해당 path를 기준으로 pages 하위 경로의 컴포넌트를 로드해 옵니다. 여기서 중요한 점은 위에서 구성한 pages 폴더 구조에 맞게 페이지 URL을 설정하고 해당 URL로 접근 시 경로에 맞는 컴포넌트를 로드하여 렌더링하는 것입니다.
 
				
					const PAGE_LIST = [
  {
    path: '건강보험자격득실확인서',
  },
  {
    path: '코로나19예방접종증명서',
  },
  ...
];

const AppRoute = () => {
  ...
  return (
    <>
      {PAGE_LIST.map(({path}) => {
        return (
          <RouteComponent path={path} />
        );
      })}
    </>
  );
};
				
			
 
위의 형태로도 URL 경로에 맞게 구성된 폴더 path에 따라 페이지 컴포넌트를 불러올 수 있었지만, 상위 컴포넌트에서 동적으로 불러와진 컴포넌트가 로딩이 완료된 시점에 따른 추가적인 처리가 필요하여 해당 코드를 훅(Hook)으로 변경하여 사용하였습니다.
 
				
					import {useState, useEffect} from 'react';

const pageImport = (path) => import(`pages${path}`);
const layerImport = (path) => import(`layer${path}`);

const useRouteComponent = (path, type) => {
  const [component, setComponent] = useState(null);

  useEffect(() => {
    const importModule = async () => {
      if (type === PAGES) {
        const {default: Page} = await pageImport(path);
        setComponent(() => Page);
      }
      if (type === LAYER) {
        const {default: Layer} = await layerImport(path);
        setComponent(() => Layer);
      }
    };
    importModule();
  }, [path]);

  return {
    component
  };
};

export default useRouteComponent;
				
			
 
훅에서는 경로와 타입을 받아 페이지인 경우에는 pages 폴더 하위 경로에서 컴포넌트를 가져오도록 하고 레이어인 경우에는 layer 폴더 하위에서 컴포넌트를 가져오도록 하였습니다.

 

여기서 추가로 이전에 만든 라우트 리스트의 경로 정보와 현재 URL 경로를 비교하여 현재 URL 경로에 맞는지 검사하는 로직 처리가 필요하였습니다. 해당 처리는 react-router에서 matchPath를 사용하여 라우트 정보에서 현재 URL 경로에 맞는 라우트를 판단하여 처리하였습니다.
 
				
					PAGE_LIST.map(({path}) => {
  const isActive = matchPath(location.pathname, {path})?.isExact;
  ...
})
				
			
 
현재 URL 경로에 맞는 Route에서 해당 경로에 맞는 컴포넌트를 훅을 통해 지연 로딩하도록 구성하였습니다. 이로 인하여 리스트 정보에서 각 경로에 맞게 컴포넌트를 구성하던 부분과 컴포넌트를 import 하던 코드 또한 정리되어 리스트 정보 또한 한결 간결하게 개선할 수 있었습니다.
 
				
					// import 건강보험자격득실확인서 from './건강보험자격득실확인서';
// import 코로나19예방접종증명서 from './코로나19예방접종증명서';
...

const PAGE_LIST = [
  {
    path: '건강보험자격득실확인서',
    // children: <건강보험자격득실확인서 />,
  },
  {
    path: '코로나19예방접종증명서',
    // children: <코로나19예방접종증명서 />,
  },
  ...
];

const PageRoute = ({path}) => {
  const {component} = useRouteComponent(path);
  return (
    <Route path={path}>
      {component ? createElement(component) : null}
    </Route>
  );
};

const AppRoute = () => {
  ...
  return (
    <>
      {PAGE_LIST.map(({path}) => {
        const isActive = matchPath(location.pathname, {path})?.isExact;
        return isActive ? <PageRoute path={path} type={PAGES} /> : <></>;
      })}
    </>
  );
};
				
			
 
페이지와 레이어를 지연 로딩되게 개선 이후 초기 로딩 페이지 파일 사이즈가 약 100KB 정도 개선되었습니다.
 
이는 추후에 추가될 페이지와 레이어 코드에도 적용되는 사항이라 코드가 추가될수록 지연 로딩 개선은 더욱 빛을 발할 것으로 예상됩니다.
 
 
 

마치며

프론트엔드 작업을 하며 대부분 사용하기 편하게 구현된 라이브러리를 많이 사용하게 된 것 같습니다. 렌더링을 간편하게 도와주는 React 부터 시작하여 페이지 라우팅, SSR까지 지원해주는 프레임워크인 Next.js를 사용합니다.
 
이번 페이지/레이어 라우팅 작업을 시작하게 된 계기도 Next.js 프로젝트에서 작업을 하다 React만 사용하여 구성된 프로젝트를 작업하다 보니 Next.js에서 주는 간편함에 대한 불편을 느껴서 시작하게 된 것 같습니다.
 
지금은 간편하게 사용하는 라이브러리의 기능들 일지라도 내부적으로 어떻게 동작할지를 파악해보고 필요한 상황에 따라 구현하여 사용할 수 있도록 학습해야겠다는 생각을 다시금 하게 되었습니다.
 

Latest Posts

Object Storage 구현을 위한 분산 Radix Tree

안녕하세요. 카카오 분산기술셀에서 분산 스토리지, DBMS 등을 개발하고 있는 yanoo.kim(김연우)이라고 합니다. 여기에서는 카카오 사내 Object Storage인 MetaKage가 이용 중인 분산 Radix Tree

TSDB as a Service, TSCoke 개발기

안녕하세요. 카카오 분산기술셀에서 분산스토리지, DBMS 등을 개발하고 있는 yanoo.kim(김연우)이라고 합니다. 여기에서는 카카오 사내의 TSDB as a sevice인 TSCoke를 개발하게 된 이유, TSCoke를

2022 테크 여름인턴십 코딩테스트 해설

2022년 카카오 여름 인턴십 코딩 테스트가 지난 5월 7일에 5시간에 걸쳐 진행되었습니다. 시간이 부족하여 문제를 풀지 못하는 아쉬움이 없도록 1시간을 늘려 테스트를