22년 말 Next.js 13이 출시되고 많은 변화가 생겼다고 한다.
물론 나는 13출시 후에 Next.js를 처음 사용해봤기 때문에 다시 공부해야하는 불편함은 없었지만, 현재 진행중인 Cake project(가제)에 Next.js를 사용하고 있으므로 Next.js13의 주인공 App 디렉토리 아키텍처
에 대해 이해하고 넘어가야겠다.
App 디렉터리는 라우트를 처리하고 뷰를 렌더링하기 위한 Next.js의 새로운 전략이다.
이 전략은 서로 다른 몇 가지의 기능을 하나로 묶어 리액트 Concurrent 기능을 최대한 활용할 수 있도록 고안되었다고 한다.
리액트 Concurrent란?
자바스크립트는 싱글스레드로, 다중 코어를 이용해서 병렬적으로 실행시키는 언어가 아니다. 그래서 이벤트 루프라는 방식을 이용해서 이 한계점을 최대한 극복할 수 있는 구조가 쓰인다. 자바스크립트의 라이브러리인 리액트도 마찬가지이다. Concurrent 모드는 이런 환경 속에서 최대한 동시성을 추구할 수 있는 방법을 도입하겠다는 의미를 담고있다.
하지만 Next.js 앱의 컴포넌트와 페이지에 대해 생각하는 방식에 큰 패러다임 변화를 가져왔고, 이 새로운 앱 빌드 방식은 많은 개선점들을 제공한다.
기존 라우팅, 렌더링 아키텍처(Page 디렉터리)의 경우 라우트별로 데이터를 가져와야 했다.
getServerSideProps
: 서버 사이드 렌더링getStaticProps
: 서버 사이드 프리 렌더링 / 증분 정적 재생성getStaticPaths
+ getStaticProps
: 서버 사이드 프리 렌더링 / 정적 사이트 생성지금까지는 페이지 단위로 렌더링 전략을 선택할 수 없었고, 대부분의 앱은 전체적으로 SSR을 적용하거나 정적 사이트 생성을 사용했다.
Next.js는 아키텍처 내에 차별적으로 라우트를 생각하는 것이 표준이 될 만큼 충분한 추상화를 만들었다.
앱이 브라우저에 도달하면 하이드레이션이 시작되고
_app
컴포넌트를 리액트 컨텍스트 Provider로 감싸서 데이터를 집합적으로 공유하는라우트를 가질 수 있다.
Hydrate ?
Hydrate는 SSR에서 사용되는 개념으로, 클라이언트 측 JavaScript가 정적 호스팅 또는 서버 측 렌더링을 통해 전달되는 HTML 요소에 이벤트 핸들러를 첨부하여 동적 웹페이지로 변환하는 기술이다.
SSR의 경우 pre-rendering을 통해 완성된 HTML을 클라이언트에 전달한다.
이렇게 서버에서 렌더링된 정적페이지를 클라이언트에 보내고
리액트는 번들링된 JavaScript코드를 클라이언트에 보낸다.
클라이언트는 전달받은 HTML과 JS코드를 매칭하는 Hydrate를 수행한다.
Hydrate란 전송받은 JS코드들이 이전에 보내진 HTML DOM 요소 위에서 한 번 더 렌더링하게 되면서 각각 자기 자리를 찾아가며 매칭되는 것이다.
Hydrate후에는 클릭과 같은 이벤트나 모듈들이 적용되어 사용자 조작이 가능해진다.
-> SSR 덕분에 사용자는 UI를 먼저 볼 수 있고, Hydrate 덕분에 JS 코드가 매칭되어 추후에 사용자 조작이 가능하다!
라우터별로 필요한 데이터를 구성하고, 렌더링을 하는 기능 덕분에
이 접근 방식은 앱에서 데이터를 전역적으로 사용해야 할 때 매우 유용하다.
이 전략을 사용하면 데이터를 앱 전체에 뿌릴 수 있지만,
모든 것을 컨텍스트 Provider로 래핑하면 하이드레이션이 앱 루트에 묶이게 된다.
즉, 해당 트리(컨텍스트 Provider 내에 있는 모든 라우트)의 브랜치를 더이상 서버에서 렌더링 할 수 없다.
여기서 레이아웃 패턴
이 도입되며 페이지 주위에 래퍼를 생성하면 앱 전체의 렌더링 전략을 결정하는 대신 라우트별로 렌더링 전략을 다시 선택하거나 해제할 수 있게 된다.
(참고: 페이지 디렉토리 상태를 관리하는 방법)
렌더링 전략을 세밀하게 정의할 수 있는 이 레이아웃 패턴은 훌륭한 솔루션이었기에, App 디렉토리에 레이아웃 패턴을 전면 배치하였다.
Next.js 아키텍처의 일급객체로서 성능, 보안 및 데이터 처리 측면에서 엄청난 개선을 이룰 수 있었다.
이 아키텍처는 페이지별 레이아웃 아키텍처를 기반으로 한다.
_app
컴포넌트도 없고, _document
컴포넌트도 없다.
이 두 컴포넌트는 모두 루트 컴포넌트 layout.jsx
로 대체되었다.
이는 전체 애플리케이션을 래핑하는 특별한 레이아웃이다.
루트 레이아웃은 전체 앱을 위한 특별한 컴포넌트지만,
다른 구성요소를 위한 루트 컴포넌트도 가질 수 있다.
이러한 컴포넌트와 규칙은 기본적으로 중첩된다.
/about
은 자동으로 /
의 래퍼 안에 중첩된다.
마지막으로, 모든 라우트에 대한 page.jsx
가 있어야 하는데, 이는 해당 URL 세그먼트에 대해 렌더링할 주요 컴포넌트를 정의하기 때문이다.
이 컴포넌트는 기본적으로 중첩되지 않고, 해당 URL 세그먼트와 정확하게 일치하는 경우에만 DOM에 표시된다.
레이아웃에 대한 공부가 필요하여~ 공식문서 내용정리
레이아웃은 기본적으로 layout.js
파일에서 React 컴포넌트를 내보내서 정의할 수 있다. 컴포넌트는 렌더링 중에 자식 레이아웃 또는 자식 페이지로 채워질 자식 프로퍼티를 받아들여야 한다.
export default function DashboardLayout({
children, // will be a page or nested layout
}: {
children: React.ReactNode;
}) {
return (
<section>
{/* Include shared UI here e.g. a header or sidebar */}
<nav></nav>
{children}
</section>
);
}
루트 레이아웃
이라고 한다. 필수 레이아웃으로, 애플리케이션의 모든 페이지에서 공유된다. 루트 레이아웃에는 HTML및 body태그가 포함되어야한다.children
prop을 사용하여 그 아래의 자식 레이아웃을 감싸준다.useSelectedLayoutSegment
를 사용할 수 있다.// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode,
}) {
return <section>{children}</section>;
}
위의 두 레이아웃을 결합하면 루트레이아웃(app/layout.js)이 대시보드 레이아웃(app/dashboard/layout.js)을 감싸고, 대시보드 레이아웃은 route segment(app/dashboard/*) 을 감싸게된다.
Next.js 13의 주인공 App 디렉토리 아키텍처에대해 간단하게 살펴보았다.
Next를 적용한 프로젝트 폴더구조를 많이 살펴보면서 다른 분들은 어떻게 적용했는지 공부해봐야겠다.