Next.js 13 버전에서 새롭게 공개된 App Router 방식은 몇 번의 업데이트를 거쳐 현재(포스팅 작성 시점 기준 13.5) 는 꽤 높은 수준의 안정성을 가지게 되었습니다. 저도 작은 프로젝트들에서 App Router 방식의 Next.js 프로젝트를 새로 시작하거나 마이그레이션 해보며 직접 사용해 보았는데, Emotion 호환 정도를 제외하곤 안정성 면에서 큰 문제를 느끼지 못했습니다. (MSW에 대한 문제도 있다고는 들었지만 직접 확인해보지는 못했습니다.)
위처럼 몇 가지 단점들이 존재하더라도 App Router는 그것을 감수하고 사용할만큼 충분히 매력적인 선택지입니다. 이번 포스팅에서는 App Router가 등장하게 된 배경과 이유, Page Router와는 어떤 부분들이 달라졌는지에 대해 개념적으로 알아보겠습니다.
App Router의 등장
Next.js는 파일 시스템(디렉토리)을 기반으로 한 라우팅을 사용하고 있습니다. 특정 규칙에 맞게 프로젝트의 파일과 폴더를 구성하면, Next.js에서 자동으로 인식하여 라우팅을 생성해주는 형식입니다. 우리가 React를 사용할때 react-router-dom
라이브러리를 사용하여 라우팅을 처리해주는 방식과는 사뭇 다르죠.
예시로 기존의 Page Router 방식은 pages/
디렉토리 내부에 라우트 이름이 될 폴더와, 그 내부에 index.jsx
파일을 만들어주면 자동으로 라우팅이 연결되었습니다. 이는 page/
디렉토리 대신 app/
디렉토리를 사용하는 App Router 방식과도 크게 다르지 않습니다.
그러나 Page Router 방식에는 몇 가지 아쉬운 점들이 있었습니다. 이후부터는 App Router에서 어떻게 바뀌었는지와 함께 다뤄보겠습니다.
불필요한 리렌더링
Page Router에서는 같은 공유 컴포넌트를 사용하는 경우에도, 페이지가 바뀌면 리렌더링이 발생했습니다. 일반적인 React의 CSR 방식에서는 쉽게 해결할 수 있는 일이었지만 SSR이나 SSG 방식을 사용한다면 근본적인 해결책을 내기 어렵기 때문에 까다로운 문제점 중 하나입니다.
여러 페이지에서 공유하는 대표적인 컴포넌트로는 사이트 로고, 메뉴, 유저 정보등을 보여주는 헤더가 있습니다. 이런 컴포넌트들에서 불필요한 리렌더링이 반복되는 것은 결국 사용자 경험에 부정적인 영향을 끼치게 됩니다. 일반적으로 SSR, SSG 방식이 CSR 방식보다는 낮은 LCP(Largest Contentful Paint, 최대 콘텐츠풀 페인트)를 갖고 있지만, 이후의 화면 전환에서는 역전이 일어나는 경우가 많기 때문에 이런 불필요한 리렌더링은 최대한 지양해야 합니다.
App Router에선 Layout을 통해 이 문제를 해결할 수 있습니다. 하위 라우터 간의 이동에서 특정 컴포넌트가 유지되어야 하는 경우, Layout으로 끌어올려 리렌더링을 방지할 수 있습니다.
실제로 App Router가 적용된 블로그의 경우, 리액트 프로파일러를 돌려보면 헤더를 제외한 컨텐츠 부분만 리렌더링이 일어나는 것을 확인할 수 있습니다.
데이터 페칭
이전까지는 getServerSideProps
, getStaticProps
, getStaticPaths
등의 서버 사이드 API를 사용하여 서버로부터 데이터를 가져와 페이지에 내려주는 방식을 사용했습니다. 각각 가변적인 데이터와 정적 데이터, 정적 페이지로 만들 동적 경로 지정 등 다양한 역할을 하고 있습니다.
위의 세 가지 API는 모두 사라졌고, 데이터를 받아오는 두 API는 fetch
로, getStaticPaths
는 generateStaticParams
로 대체되었습니다.
물론 우리가 흔히 사용하던 그 fetch와 같으나, Next.js에서 자체적인 캐싱 정책을 추가하여 완전히 동일하지는 않습니다. 예를 들면 이런식입니다.
> fetch(url, options)
fetch(
`https://example.com/api`,
{
cache: 'force-cache' | 'no-store',
next: { revalidate: false | 0 | number },
tags: ['post']
}
)
cache: 'force-cache'
는 강제적으로 캐싱을 사용하겠다는 의미입니다. 별다른 객체를 추가하지 않으면 기본값으로 설정되어 있으며 revalidate를 통해 refresh 주기를 지정해 줄 수 있습니다. false로 설정하면 무기한으로 캐싱을 진행하며 이 경우 getStaticProps
와 동일하게 동작합니다.
'no-store' 의 경우 말 그대로 캐싱을 사용하지 않는 옵션입니다. 항상 데이터를 새로 요청하여 가져옵니다.
이처럼 Next.js 자체적으로 프레임워크 레벨에서 강하게 결합되어 있는 fetch 객체를 지원하기 때문에 기존 서버 사이드 API 대신 더 직관적이고 자바스크립트에 익숙한 코드 형식으로 개발할 수 있게 되었습니다. 개인적으로는 이러한 변화는 좋은 방향으로 가는 과정이라고 생각합니다. (기존 Next.js에 이미 익숙해졌다면 나쁜 소식이겠지만... 😥)
리액트의 서버 컴포넌트화
이 부분은 아직 충분한 근거 자료를 찾지 못하였습니다.
추측이 다수 포함되어 있으니 정확한 정보를 아시는 분께서는 댓글로 남겨주시면 감사하겠습니다.
React 18 버전부터는 리액트의 서버 컴포넌트적 성향이 강화되었습니다. 아키텍쳐 레벨의 개선을 통해 서버 사이드 렌더링 성능 향상이 이루어졌고, 앞으로는 CSR을 넘어 SSR에서도 찰떡같이 어울리는 라이브러리로 발전할 것이라는 제스쳐를 어필한다는 느낌이었습니다. 특히 18 버전에서의 React.lazy
와 Suspense
의 개선은 이러한 느낌에 확신을 주었습니다.
왜 React 18이 SSR에 특히나 큰 신경을 썼는지에 대해선 Dan Abramov의 글에 자세히 설명되어 있으니 한 번 참고해보셔도 좋을 것 같습니다.
공교롭게도, Next.js에서 13버전을 출시하며 작성한 글에서는 기존의 Page Router가 데이터 페칭과 페이지 메타 데이터를 위한 스트리밍과 같은 최근 React의 발전 방향에 부합하지 않았다고 밝혔습니다. 이는 Next.js가 추구하는 장기적인 비전에도 어긋나는 상황이었기 때문에, 결국 기존 Page Router의 핵심 아키텍쳐에 큰 변화를 주게 되었습니다. 그 결과물이 바로 App Router입니다.
이 글에서 Next.js의 개발진들은 App Router가 서버 컴포넌트나 Suspense와 같은 최신 React 기능들에 기반한 설계를 구축하게 되었다고 말합니다. 즉, 기존의 Page Router가 클라이언트에서 동작할 수 있는 몇몇 서버 기능들을 추가한 느낌이었다면, App Router부터는 본격적으로 서버에서 클라이언트의 방향으로 '스트리밍' 해주는 구조가 되었다고 할 수 있겠습니다. 단, 기존 CSR의 장점들은 최대한 챙겨가면서요.
마치며
이번 글에서는 각 라우팅 방법의 구체적인 사용 예시보다는, 충분히 좋아보이는 Page Router가 있음에도 App Router가 등장하게 된 배경에 중점을 두고 작성해보았습니다. Next.js를 stable 버전의 App Router로 먼저 입문하여 이전의 맥락을 알지 못했지만, 이번 글을 작성하며 찾아본 내용들이 전체적인 흐름을 잡는데 큰 도움이 되었습니다.
특히 마지막 문단의 내용에 관해서는 관련한 자료를 찾기가 정말 어려웠던터라 객관적인 사실 대신 주관적인 추측이 많이 포함되어 있습니다. 새로운 리액트 버전이 등장했던 당시 상황과 함께 생각하여 내린 결론이기에 아주 정확한 내용은 아닐 것이라고 생각합니다. 혹시 해당 내용이 등장한 정확한 맥락을 알고 계신 분께서는 지식을 나누어주시면 정말 감사하겠습니다.
마지막으로, 두서없는 글 읽어주셔서 감사합니다.
'이론 > Frontend' 카테고리의 다른 글
자바스크립트로 비동기 처리하기 : Callback과 Promise (3) | 2023.10.24 |
---|---|
Next.js 13에서 Kakao 로그인 처리하기 with Firebase (1) (0) | 2023.10.16 |
Next.js로 metadata 구성하기 (1) | 2023.09.24 |
리액트에서 DOM Node를 찾는 방법 (0) | 2023.08.05 |
자바스크립트에서 값 비교하기 (0) | 2023.04.23 |
댓글