[번역] 우리가 Next.js로 리액트 로딩 시간을 70% 개선한 방법

Minjeong Koo·2022년 11월 15일
44

좋은 아티클 번역

목록 보기
7/8
post-thumbnail

원문: How We Improved React Loading Times by 70% with Next.js

CRA에서 Next.js로 전환하여 초기 페이지 로드 시간을 70% 단축하고 새로운 수준의 개발자 경험을 제공했습니다.
Causal은 기본 산술에서부터 10억 개의 계산 재무 모델에 이르기까지 모든 것을 처리할 수 있는 다차원 스프레드시트입니다. Causal의 프런트엔드는 2019년에 CRA(Create React App)를 사용하여 구축되었으며, 최소한의 초기 설정만 필요했고 이 설정을 빠르게 반복하여 사용했습니다. 하지만 고객의 규모와 서비스의 복잡성이 커지고 성능이 더욱 중요해지면서 CRA에서 지원하는 설정만으로는 해결할 수 없는 한계에 도달했습니다. 가장 큰 문제는 CRA가 기본적으로 여러 페이지 앱 간의 경로 분할(route splitting)을 지원하지 않아 페이지 로드 시간이 답답할 정도로 느렸다는 것입니다. 우리는 이러한 문제를 해결하기 위해 Next.js로 전환하여 초기 페이지 로드 시간을 최대 70% 줄이고, 새로운 수준의 개발자 경험을 제공했습니다.

내용

  1. Next.js란?
  2. CRA에서 Next.js로 마이그레이션 하기
  3. 결과
  4. 다음 단계

Next.js란?

Next.js는 거대한 리액트 애플리케이션을 만들기 위한, 빌드 도구와 런타임 라이브러리들이 함께 제공되는 프레임워크입니다. Next.js는 CRA와 동일한 기능들을 제공하면서 CRA에 없는 주요 기능들도 기본으로 제공하고 있습니다. 대표적인 기능으로는 페이지 라우팅, 페이지 내용 기반 지능형 pre-loading, 하이브리드 정적 및 서버 사이드 렌더링이 있습니다.

CRA에서 Next.js로 마이그레이션 하기

2022년 중순, 우리는 CRA에서 Next.js로 마이그레이션 하는 것의 장점이 크기 때문에 충분히 시간 투자를 할 가치가 있다고 판단했습니다. 특히 라우팅 및 웹팩 빌드를 직접 구성할 필요가 없어지는, 내장된 페이지 라우팅 프리미티브(routing primitive) 기능이 있다는 사실이 기뻤습니다.

라우팅

이전에는 CRA에서 직접 자체적으로 설정해야 했습니다. 앱의 각 페이지에 대해 라우팅 된 컴포넌트를 명시적으로 설정하는 거대한 routes.tsx 파일을 포함하여 react-loadable 및 react-router+react-router-dom을 사용하여 설정했습니다.

import Loadable from "react-loadable";
import { Route, Switch } from "react-router-dom";

const EditorLoadable = Loadable({
  loader: () => import(/* webpackChunkName: "routes-editor" */ "pages/editor"),
  loading: ChunkLoading,
});

export function RouteSwitch() {
  return (
    <Switch>
      <Route path={`/model/:id/edit`} render={() => <EditorLoadable />} /></Switch>
  );
}

CRA와 비교했을 때 Next.js의 장점 중 하나는 Next.js가 자체 통합 링크 및 라우팅 솔루션인 next/router와 함께 제공된다는 것입니다. default-export된 React 컴포넌트가 있는 pages/model/[:id]/edit.tsx에 파일을 배치하는 것은, 모든 Next.js가 해당 path에서 페이지를 렌더링하기 위해, URL의 id가 가르키는 id prop을 알아야 한다는 것을 의미합니다.

또한 내장된 Next.js 웹팩 설정은 자동으로 페이지를 자체 번들로 분할합니다. 즉, 로컬 개발을 위해 페이지를 방문하면 해당 페이지에 필요한 번들 콘텐츠만 빌드하면 된다는 것을 의미합니다. CRA는 코드 분할을 지원하지만, 경험상 Next.js 설정은 기본적으로 로컬 재빌드를 훨씬 더 빠르게 할 수 있습니다.

스타일

Causal 코드베이스의 오래된 많은 CSS 파일들은 팀에서 CSS 모듈 모범 사례를 표준화하기 전에 작성되었습니다. 이러한 파일의 많은 곳에서 "순수하지 않은(impure)" CSS selector를 사용했습니다. 즉, 페이지의 다른 곳에 있는 컴포넌트에 의해, 현 페이지에서 렌더링되는 요소(element)까지 영향을 미칠 수 있었습니다.

예를 들어 이전의 Button 컴포넌트는 의도치않게 페이지의 모든 버튼을 타겟으로 지정했습니다.

// styles/button.scss
button:disabled {
  cursor: not-allowed;
}
// components/Button.tsx
import "styles/button.scss";

export function Button(props) {
  return <button {...props} />;
}

우리는 가능한 모든 곳에서 global CSS 스타일을 CSS 모듈로 전환했습니다. 이를 통해 컴포넌트는 사용중인 스타일에 대해 정확하게 알 수 있었습니다.

예를 들어 Button 컴포넌트를 CSS 모듈에서 범위가 지정된 클래스 이름으로 전환했습니다.

// components/Button/styles.module.scss
.button:disabled {
  cursor: not-allowed;
}
// components/Button/index.tsx
import styles from "./styles.module.scss";

export function Button({ className, props }) {
  return <button className={cx(styles.button, className)} {...props} />;
}

참고: "순수한(pure)" CSS 모듈로 전환하고 나서 Next.js로 전환이 완료되기 전임에도 CRA 앱의 빌드 시간이 크게 향상되었습니다. 또한 많은 .scss 파일들도 @use와 @extend SCSS 지시문을 사용하여 공유되는 .scss 파일들을 활용하여 스타일을 구축했습니다. 이러한 지시문은 공유 파일을 포함하는 각 파일의 일부로 다시 빌드 되도록 하여 일부 큰 파일에 대해 각각 몇 초의 빌드 시간을 발생하게 되었습니다!

자세한 내용은 순수한(pure) 모듈에 대한 Next.js 토론 답변을 참조하십시오.

배포

Next.js가 로컬에서 작동하면, 다음 스텝은 배포 방식을 변경하는 것이었습니다.

여기서 CRA와 Next.js는 근본적인 차이점이 있습니다. CRA의 빌드 output은 정적 파일일 뿐이므로 비교적 쉽게 처리할 수 있습니다. Next.js의 빌드 ouput에는 일부 정적 파일이 포함되지만 별도의 서버를 실행하기 위한 코드도 포함될 수 있습니다. 이 서버는 리디렉션, 동적 페이지 서버 사이드 렌더링과 정적 페이지 제공을 담당합니다.

새로운 Next.js 프런트엔드 배포를 위한 옵션을 검토할 때, 우리는 세 가지 가능성을 고려했습니다.

  1. Next.js에 서버 사이드 렌더링을 사용하지 않고, next export를 사용하여 빌드하고, output 또한 CRA의 static output과 동일하게 처리합니다.
  2. 백엔드를 가리키는 Vercel에서 전체 프런트엔드를 호스팅합니다(GCP에서 호스팅).
  3. Next.js 서버에 대한 커스텀 도커 이미지를 작성하여 백엔드 및 기타 서비스와 함께 GCP에 호스팅합니다.

각각의 옵션에는 장단점이 있었습니다.

next export

  • 장점: 설정이 거의 없음(CRA 빌드 output과 동일)
  • 단점: 서버 사이드 렌더링을 지원하지 않음

Vercel로 호스팅

커스텀 도커 이미지

  • 장점: 서버 사이드 렌더링에 유연함 (직접 DB 연결 가능, GCP의 코로케이션 덕분에 백엔드 API 호출이 매우 빠름)
  • 장점: 필요한/사용되는 리소스를 가장 세밀하게 제어 가능
  • 단점: 설정이 제일 많이 필요함 (Vercel 예제를 제공하지만 바로 사용할 수 없음, Kubernetes의 라우팅/네트워킹, 확장 등 모두 추가 설정이 필요함)

우리는 최대한 유연하게 사용하고 싶었기 때문에, 3번 옵션(커스텀 도커 이미지)을 선택했습니다. (그래도 여전히 Varcel에 배포하고 있습니다, 나중에 자세히 설명하겠습니다!) 우리는 몇 페이지 걸쳐 적은 횟수의 서버 사이드 렌더링을 수행하며, 다른 서비스와 통신하는 데 필요한 네크워크 거리를 최소한으로 둔 덕분에, 지금까지의 서비스의 성능이 우수하다는 것을 알게 되었습니다.

앱 미리보기

위에서 설명한 대로 Vercel은 프로덕션 배포에 적합하지 않았지만, 앱 미리보기(preview app) 여전히 유용합니다. Vercel 빌드 프로세스를 설정하는 데에도 몇 가지 추가적인 해결 방법이 방법이 필요했지만(앞서 언급했던 Yarn2 지원 부족, 프런트엔드에서 사용되는 공통 패키지 빌드) 이점은 상당히 많습니다. GitHub 리퍼지토리에 푸시되는 모든 커밋은 이제 Vercel에서 스테이징 백엔드를 가리키는 미리 보기 앱으로 빌드되고 배포됩니다.

이를 통해 프런트엔드 변경 사항에 대한 코드 리뷰 경험이 크게 개선되었습니다. 리뷰어가 로컬에서 브랜치를 pull해서 테스트하는 대신 리뷰 중인 PR의 링크를 클릭하기만 하면 브랜치가 프로덕션 환경에서 어떻게 보일지 미리 볼 수 있습니다.

이 변경에 Next.js가 필요한 것은 아니었지만, 자체 프레임워크에 대한 Vercel의 기본적인 지원이 제공된 덕분에 매우 수월했습니다.

결과

Next.js로 전환하면서 엔드 유저와 개발자 경험 모두가 상당히 개선되었습니다.

Causal 모델은 일반적으로 소수의 사람들이 만들지만 수십 명의 사람들이 보고, 이 사람들은 모델 대시보드를 봅니다. 이러한 대시보드 로드 시간이 32% 감소했습니다(2.6초 → 1.5초)! 고급 서버 사이드 렌더링(예: getServerSideProps 사용)에 대한 노력도 없이 말입니다.

단순한 페이지는 훨씬 더 드라마틱하게 속도가 향상 되었습니다. 예를 들어, 우리 홈페이지(my.causal.app)는 로딩 중인 상태에서 로드 완료된 상태로의 필요한 전환을 제외하고, 레이아웃 전환없이 71% 더 빠르게 로드되게 되었습니다(1.7초 → 0.5초).

성능 상의 이점은 단순히 사용자 경험에만 좋은 것이 아닙니다. Next.js는 CRA보다 훨씬 빠른 개발 경험을 제공하기 때문에, 개발자는 30%(또는 그 이상!) 앱을 더 빠르게 시작(start-up)할 수 있습니다. 또한 빠른 새로고침이 가능하기 때문에 작은 UI 수정을 빠르게 반복할 수 있는 획기적인 역할도 하게 됩니다. 지금까지 가장 크게 개선한 것은 코드 리뷰 경험을 크게 개선한 pull request preview 앱이었습니다. 프런트엔드 코드 수정 사항을 미리 보는 데 몇 분이 아니라 몇 초가 소요되므로 적은 규모의 pull request에 대해 더 자주 검토할 수 있었고 고객 성공 팀이 개발 프로세스의 초기에 피드백을 제공할 수 있게 되었습니다.

다음 단계

Next.js에서 돌아가는 앱을 보게 되어 기쁩니다. 페이지 로드는 훨씬 빨라졌고 로컬 빌드는 시작하는 데 몇 분 대신 몇 초가 걸리며, 유지해야 하는 웹팩 설정의 양은 수백 줄이 아닌 수십 줄입니다.

우리는 곧 익명의 방문자가 일반적으로 보는 임베디드 차트와 테이블을 시작으로, 더 많은 서버 사이드 렌더링을 구현할 계획입니다. 로드 시간이 빨라짐으로서 이러한 사용자 경험도 크게 좋아질 것으로 기대합니다.

물론, 모던 웹 앱의 성능은 처음 로드하는 시간보다 훨씬 더 중요합니다. 훨씬 더 중요한 것은 사용자 인터렉션의 성능입니다. 우리는 복잡한 그리드와 차트 및 테이블을 렌더링하는 데이터가 많은 애플리케이션이기 때문에, Causal에서 최적화하기가 특히 어렵습니다. 향후 블로그 게시물에서 이러한 성능 문제를 해결하는 방법에 대해 자세히 공유할 것입니다.

저희와 함께 이러한 과제를 해결하는 데 관심이 있으시다면 Causal 팀에 합류하는 것을 고려해보세요. 저희의 career 페이지를 확인하거나 lukas@causion.app으로 이메일을 보내주세요.

이 마이그레이션에 중요한 도움을 주신 Joshua Goldberg님께 감사드립니다!

profile
Software Developer

3개의 댓글

comment-user-thumbnail
2022년 11월 17일

정말 좋은 글이네요!

답글 달기
comment-user-thumbnail
2022년 11월 24일

Thanks a lot for sharing the awesome post. I have also wants to develop the same kind of thing for this page https://www.mtractionenterprise.com/android-app-development , by using app development.

답글 달기