[FDBS] App router 마이그레이션 진행기

Jay ·2023년 9월 17일
0
post-thumbnail
post-custom-banner

1. 서론

개인 프로젝트가 오랜 기간 진행되며 Next.js에는 많은 업데이트가 일어났다.

13.4 버젼에서는 app router 및 server component 등이 Stable 버젼으로 전환되었고, 이후 버젼에서는 pages router에 대한 지원이 중단된다는 소식을 들었다.

고민끝에 Pages 디렉토리에서 App 디렉토리로 마이그레이션을 결정하였다.

어떤 이유에서 마이그레이션을 진행하였고, 어떤 과정을 통해 무엇을 얻었는지 그 과정을 기록해 보았다.

2. Why App router?

App router로 전환하면 무엇을 얻을 수 있고 한계는 무엇인가?

App router의 핵심은 서버 컴포넌트의 도입에 있다.
서버 컴포넌트란 서버사이드에서 컴포넌트가 실행된다는 것이다.

RSC를 도입함으로써 얻는 이점을 요약하자면, 첫 번째로 코드가 실행되는 위치를 정의할 수 있으며, 두 번째로 스트리밍(Streaming)을 통해 서버사이드에서 데이터가 준비되는 대로 바로 클라이언트에서 그 데이터를 사용해 렌더링을 할 수 있게 된다는 것이다.

이를 통해 어플리케이션의 퍼포먼스를 크게 개선할 수 있게 된다.

물론 위 사진에서 볼 수 있듯이, 이러한 퍼포먼스 상의 이점은 공짜가 아니다. 기본적으로 어플리케이션의 복잡성이 높아지고, CSS-in-js를 사용하지 못한다.

하지만 대부분의 페이지가 정적인 페이지로 이루어진 FDBS는 TailwindCSS를 사용하고 있으며, 높아지는 복잡성에도 불구하고 서버 컴포넌트의 도입으로 가져올 수 있는 성능상의 큰 개선이 더 가치있다고 생각해서 App router로 마이그레이션을 결정하였다.

3. 마이그레이션 개요

적지않은 사이즈의 코드베이스를 지닌 프로젝트였기에 Next.js Manual을 숙지하는 것이 필수적이었다.

https://nextjs.org/docs/app/building-your-application/upgrading/app-router-migration

위 페이지를 통해 Vercel에서 상세한 마이그레이션 가이드를 제공하긴 하지만, app router로 전환하며 pages router와는 사소한 부분에서 차이점이 생기기 때문에 직접 에러들을 겪어가며 마이그레이션을 진행할 수 밖에 없어 많은 시간이 소요되었다.

주요 작업은 다음과 같다.

  • 서버 컴포넌트 도입 및 클라이언트와 서버 컴포넌트 경계 구분('use client')
  • api route 네임드 익스포트로 전환
  • layout.js, error.js 등을 위한 폴더 구조 변경
  • 변경된 메소드 적용 (useSearchParams, next/navigation 등)
  • 데이터 직렬화(serialization)

4. 주요 에러

Serialization error

Unexpected token in JSON at position 0 error 

Servercomponent의 렌더링 결과물은 JSON 형태로 클라이언트에 전송되어 진다.
따라서 JSON 형태로 변환할 수 없는 데이터가 포함될 경우, 다음과 같은 에러가 발생한다.

해당 에러의 경우 Date format이 문제였는데, 재귀적으로 JSON 객체를 순회하는 함수를 직접 help 함수로 만들어 문제를 해결하였다.

5. 비교

번들 사이즈 감소

pages router

├ ● /authors/[page]                           496 B           164 kB
├ ● /authors/name/[slug]                      1.03 kB         165 kB
├ ○ /enter                                    2.87 kB         187 kB
├ ● /fictions (ISR: 10000 Seconds) (1695 ms)  4.09 kB         168 kB
├ ● /fictions/[id]                            15.4 kB         266 kB
├   └ css/dfdeab7268d20d16.css                1.17 kB
├ λ /fictions/[id]/edit                       3.88 kB         261 kB
├ ○ /fictions/[id]/history                    9.41 kB         167 kB
├ ○ /fictions/[id]/revision/[Rid]             2.55 kB         160 kB
├ ○ /fictions/create                          3.26 kB         260 kB
├ ○ /profile                                  1.44 kB         159 kB
├ ○ /profile/edit                             2.13 kB         186 kB
├ ○ /ranking                                  337 B           158 kB
├ ○ /search                                   336 B           158 kB
├ ● /search/genre/[search]/[page]             476 B           164 kB
├ ● /search/keyword/[search]/[page]           491 B           164 kB
├ ○ /search/title/[search]                    551 B           164 kB
├ ● /search/type/[search]/[page]              485 B           164 kB
├ λ /server-sitemap.xml                       252 B           158 kB
└ ○ /translation                              3.66 kB         161 kB
+ First Load JS shared by all                 165 kB
  ├ chunks/framework-4b8fae3d5775a6e3.js      45.2 kB
  ├ chunks/main-e4ef111f937f170f.js           29.7 kB
  ├ chunks/pages/_app-09c5c30e287db1b2.js     80.7 kB
  ├ chunks/webpack-8c23aa7e9c2d8264.js        1.65 kB
  └ css/5a8790f6cfc8f0bb.css                  8.13 kB

-----

app router
...
├ λ /authors/[page]                        189 B          98.8 kB
├ λ /authors/name/[slug]                   191 B          98.8 kB
├ ○ /enter                                 3.75 kB         127 kB
├ ○ /fictions                              2.6 kB          106 kB
├ λ /fictions/[id]                         12.6 kB         206 kB
├ λ /fictions/[id]/edit                    6.62 kB         194 kB
├ λ /fictions/[id]/history                 9.53 kB         107 kB
├ λ /fictions/[id]/revision/[Rid]          2.99 kB          89 kB
├ ○ /fictions/create                       5.96 kB         194 kB
├ ○ /glossaries                            2.46 kB        88.4 kB
├ ○ /profile                               1.48 kB        98.8 kB
├ ○ /profile/edit                          3.26 kB         116 kB
├ ○ /search                                145 B          81.5 kB
├ λ /search/category/[search]/[page]       189 B          98.8 kB
├ λ /search/keyword/[search]/[page]        189 B          98.8 kB
├ λ /search/title/[search]/[page]          189 B          98.8 kB
├ λ /search/type/[search]/[page]           189 B          98.8 kB
└ ○ /translation                           6.17 kB         157 kB
+ First Load JS shared by all              81.3 kB
  ├ chunks/176-8ce2a963be66a148.js         28.8 kB
  ├ chunks/fd9d1056-07a2e00a75a9d8a5.js    50.5 kB
  ├ chunks/main-app-bf086637b08fbbd6.js    218 B
  └ chunks/webpack-6b7de4d6763fbb0a.js     1.83 kB
  ...

우선 첫 페이지 진입시에 로드되는 JS 번들의 사이즈가 165kb에서 81.3kb으로 절반이하로 줄어들었다. 또한 서비스의 핵심 페이지인 '/fictions/[id] ' 페이지에서도 266kb에서 206kb로 번들 사이즈가 크게 감소하였다.

첫 페이지 렌더링시 fetch되는 데이터와, Script, Analytics 등의 코드가 실행되는 위치가 클라이언트에서 서버로 옮겨짐에 따라 관련 코드들이 번들에 포함될 필요가 없어져 코드가 줄어든 것이다.

이런 방식으로 많은 라우팅 그룹(페이지)에서 최상단 컴포넌트를 서버 컴포넌트로 전환하는 방식으로 번들 사이즈를 크게 감소시킬 수 있었다.

6. 후기

app router로 마이그레이션하며 얻은 점은 다음으로 요약할 수 있다.

클라리언트 번들 사이즈 감소,
간소화된 메서드,
metadata 등의 도입으로 외부 라이브러리 의존도 감소

하지만, pages router에선 지원되던 shallow routing 등의 일부 기능이 사라진 점이나(https://github.com/vercel/next.js/discussions/48110)
server component 사용을 위해 컴포넌트 트리가 복잡해졌으며, Context API 사용에 제약이 생긴 점은 다소 아쉬운 부분이다.

하지만 Vercel 측에서도 대부분의 이슈들에 대해 인지하고 있고 로드맵에 이를 포함시킬 내용이라고 답변을 확인할 수 있다는 점에서 향후 이런 점들을 점차 보완해 나갈 수 있을 것으로 예상된다.

profile
Jay입니다.
post-custom-banner

0개의 댓글