Next.js 13 에서는 기존 page/
디렉토리와 유사한 형태로 app/
이라는 것을 소개하였다. 아직은 Beta 버전이라고 Production 에는 적용하지 말라고는 하지만 이건 내 개인 프로젝트니 한번 도입해 보기로 했고 어떤 특징이 있는지 살펴보고자 정리하게 되었다.
Next.js 13 은 page/
디렉토리를 보존하여 하위호환성을 유지함과 더불어 이 app/
디렉토리를 도입하였다.
여러 이점을 얻게 되었지만 개인적으로는 크게 다음과 같이 요약해보고자 한다.
Server Component
아키텍쳐 지원fetch
API 로 통일한 새로운 Data Fetching
방식을 지원Streaming
지원을 통한 더 나은 반응성feat. React Server Component 지원
app/
경로에 있는 React 컴포넌트는 기본적으로 Server Component 로 동작한다.
React Server Component 는 전통적인 SSR(Server Side Rendering) 방식과 다르게 (hydration 을 위한) 컴포넌트 코드가 클라이언트에 내려가지 않고 서버에서만 동작하고 그 결과만 내려준다. 특히나 용량이 큰 라이브러리에 대한 의존성을 포함하는 경우 의존성 라이브러리에 대한 번들 크기를 크게 절약할 수 있게 된다.
SSR 과 Server Components 는 대체 관계가 아님을 유의한다.
Data Fetching with React Server Components 동영상에서 Dan 아저씨가 얘기하는 것처럼 클라이언트에서 서버로부터 Data 를 Fetching 하고 hydration 하는 과정들이 서버에서 빠르게 처리된 후 결과만 내려주는 방식으로 전환되는 것이다.
자세한 내용은 차치하고라도
1) Server Component 지원을 통해 클라이언트에 전송할 JavaScript 번들 크기를 줄여주게 되었고 2) 클라이언트 컴포넌트에서의 비동기 Data fetching 과정을 서버단으로 이관함으로써 페이지 로딩 속도를 개선할 수 있게 되었다. 더불어 3) Server 자원(File, DB, ...)을 바로 접근하는 컴포넌트를 작성할 수 있다는 점과 4) Server Component 로 작성된 하위컴포넌트가 전체 리렌더로 Tree 상태 훼손 없이 해당 컴포넌트만 받아와 갱신할 수 있다는 점이 성능을 올려주는 요소로 작용한다.
다만 Server Component 의 기능은 기존 React Component 대비 제한적이다. useEffect 와 같은 hook 을 사용할 수 없다. hook 은 클라이언트에서 동작하는 기능이기 때문이다. hook 을 사용하는 경우 Next.js 는 친절하게 에러코드를 내려준다. 이 경우 코드 상단에use client
를 명시하기만 하면 클라이언트 컴포넌트로 동작하게 할 수 있다. (당연 서버 컴포넌트로서의 이점은 누릴 수 없다)
특히나 fetch
API 하나로 데이터를 가져오고 캐싱하고 revalidate 까지 하는 작업을 할 수 있게 되어 기존의 getStaticProps
, getServerSideProps
, getInitialProps
를 하나의 API 로 대체할 수 있게 되었다.
// 임의로 무효화 할 때까지는 요청이 캐싱 (기본값)
// (=`getStaticProps`.)
fetch(URL, { cache: 'force-cache' });
// 모든 요청을 다시 가져옴
// (=`getServerSideProps`)
fetch(URL, { cache: 'no-store' });
// 일정 시간 동안만 캐싱됨
// (=`getStaticProps` with the `revalidate`)
fetch(URL, { next: { revalidate: 10 } });
주의할 사항은
app
에서는 getStaticProps, getServerSideProps, getInitialProps 를 지원하지 않는다. fetch 로 이를 대체하여 사용해야 한다. 특히나 관련 내용(SSR) 검색 시 아직은 Next.js 13 이전 내용이 나와서 헷갈릴 수 있으므로 유의한다.
위와 같은 기능을 제공하기 위해 native Web API 인 fetch
는 각각 다음과 같이 확장되었다.
1) React Server Component 에서는 중복된 요청을 제거해주는 처리를 자동으로 해주도록 확장되었다.
2) Next.js 에서는 fetch 요청 시 fetch(URL, { cache: 'force-cache' });
캐싱과 무효화 옵션을 지정할 수 있도록 확장되었다.
Next.js 에서 SSR 이 동작하는 방식은 페이지 요청 시 1) 서버에서 Data 를 수잡하여 2) HTML 로 만들고 3) 이를 클라인트로 다운로드하여 뿌려준 후 4) hydration 과정을 통해 Interaction 이 가능한 페이지로 만든다.
(이미지 출처: https://beta.nextjs.org/docs/data-fetching/streaming-and-suspense)
이 SSR 과정을 통해 생성된 HTML 은 사용자에게 기본적인 페이지 컨텐트를 빠르게 보여주긴 했지만 이것으로 끝난게 아니다. 전체 페이지에 필요한 데이터를 모두 받아서 각 페이지 요소를 다시 구성하고 인터랙티브한 페이지를 만들기(hydration)까지 페이지는 사실 블로킹 상태가 된다.
(이미지 출처: https://beta.nextjs.org/docs/data-fetching/streaming-and-suspense)
Streaming 은 서버에서 전체 페이지를 모두 구성하여 클라이언트로 내려주는 대신 페이지의 각 구성 요소를 나누어 전달 받을 수 있게 한다. 중요한 컨텐츠들은 먼저 내려주고 덜 중요한 컨텐츠들은 조금 늦게 내려주고 그 대신 로딩 상태를 보여주는 형태이다.
(이미지 출처: https://beta.nextjs.org/docs/data-fetching/streaming-and-suspense)
이를 위해 React 18 의 Suspense 기능으로 특정 영역을 Wrapping 하는 방식을 적용할 수도 있지만
<Suspense fallback={<Loading />}>
<SomeComponent />
</Suspense>
Next.js 는 loading.js(tsx) 라는 특별한 예약 파일을 정의해 놓으면 app
폴더의 각 구성 요소별 로딩 상태를 보여줄 수 있도록 기능을 제공한다.
// 예) app/dashboard/loading.tsx
export default function Loading() {
return <p>Loading...</p>
}
이렇게 함으로써 페이지를 구성하는 모든 요소들을 한꺼번에 Fetch 하고 hydration 될 때까지 블로킹 된 상태가 되는 것이 아니라 중요한 요소부터 순차적으로 완료 처리가 가능해진다.
이로 인해 컨텐트를 처음 렌더링 하는 시점(FCP)과 사용자가 인터랙티브한 시점(TTI)을 앞당겨 사용자에게 더 빠른 반응성을 제공할 수 있게 된다.
Next.js 13 이 2022년 10월 23일 정식 릴리즈 되었고 사실 그전 부터 이러한 내용이 많은 사람들에게 알려진 만큼 꽤 뒷북치는 내용이 될 것 같긴하다.
다만 사이드 프로젝트를 진행하면서 실상 Next.js 를 처음 경험해 보는 셈이고 Next.js 를 이곳 저곳 훑어보게 되었다. 사실 지금의 내용은 Next.js 공식 문서에서는 훨씬 더 방대하고 자세하게 설명되어 있다. (진짜 문서화 잘 해놓음)
난 좀더 빠르고 쉽게 내용을 전달하고자 최대한 간단히 정리하려고 했는데... 나의 뇌피셜에 의한 빈약한 근거를 신뢰성이 있는 자료로 채워나가려다 보니 본의 아니게 길어져 버렸다.
인상 깊었던 점은 Next.js 가 단순히 좀 인기 있는 프레임워크 정도라고 생각하고 있었는데 React 팀과 공조하여 더 나은 오픈 소스 생태계를 만들며 영향력을 발휘하고 있다는 점이었다. 이런 부분은 Next.js 가 한 순간의 인기로 끝나지 않고 지속적으로 발전해 갈 것으로 기대하게 만드는 부분이었다.