
라우팅 그룹 - URL을 헤치지 않는 투명 망토
우리 프로젝트가 화려한 디자인의 마케팅용 랜딩 페이지와, 관리자 대시보드를 동시에 포함해야 한다면
아마 app/ 폴더 안에 수 십개의 폴더가 뒤섞여서 어디가 사용자에게 보여지는 화면이고, 어디가 관리자 화면인지
구분하기 어렵고, 이를 하나의 레이아웃에서 조건문으로 처리하다 보면, 코드는 금새 엉망이 되고 말 것입니다.
따라서, Next.js의 라우팅 시스템이 제공하는 Route Groups와 Private Folders의 개념에 대해 알아봅니다.
Route Groups는 URL 주소에 전혀 영향을 주지 않으면서 개발자에게만 보이는 폴더를 만들 수 있게 해줍니다.

Private Folders는 라우팅 시스템으로부터 폴더를 완전히 숨겨주는 비밀 금고 같은 역할을 합니다.

먼저, marketing 랜딩 페이지와 dashboard 폴더를 나누기 위해 폴더 구조를 다음과 같이 새롭게 구조화합니다.

여기서 Next.js는 어떤 기준으로 layout을 선택하는지에 대해 작동 원리를 알아보겠습니다.
핵심 규칙은 URL은 괄호를 무시하지만 layout 시스템은 괄호를 인식한다는 점입니다.
사용자가 Root 경로로 접속하면, Next.js는 실제 파일인 marketing 그룹의 page.tsx 파일을 찾아내고,
자신의 직계 부모 레이아웃을 찾는데, 가장 가까운 레이아웃을 적용시킵니다. 이 때 marketing 폴더 기준으로 찾습니다.
반대로 사용자가 dashboard 경로로 이동하면, (app) 폴더 내부의 dashboard 산하의 page.tsx 파일을 호출하고,
이때로 똑같이 직계 부모 레이아웃을 찾으며, 다른 그룹 폴더로 침범할 수 없습니다.
결과적으로 Next.js는 파일 시스템의 물리적인 폴더 계층 구조로 가장 가까운 직계 부모 레이아웃을 적용하며
이것이 바로 URL은 깔끔하게 유지하면서도 디자인이 서로 밀어내어 깨지지 않고 완벽하게 분리될 수 있는 아키텍처입니다.
이제 두 번째 개념인 Private Folders에 대해 알아봅니다.
특정 폴더를 라우팅 시스템에서 완전히 제외하고 싶을 때가 있습니다. 유틸 함수나, 공통 컴포넌트 함수가 그 예입니다.
이 때 폴더 이름 앞에 _ 기호를 붙이면, 그 폴더는 라우팅 시스템에서 아예 제외합니다.
예시로, 특정 URL 맵핑 폴더 안에 _components 폴더를 두어, 특정 페이지 전용 컴포넌트를 불러올 수도 있고
app/ 폴더 안에 _utils 폴더를 두어 공통으로 사용하는 유틸 함수를 보관할 수도 있겠습니다.
use client - 인터렉티브한 Next.js의 완성
현재까지 학습 내용으로, Next.js에서 React Hooks나, 이벤트 핸들러를 사용할 수 없었던 이유는
Next.js가 기본값으로 서버 컴포넌트로 동작하기 때문입니다.
다시 Next.js의 특징을 복기해보면, Next.js는 React와 달리 모든 페이지와 컴포넌트는 서버에서 먼저 랜더링됩니다.
서버는 브라우저로 Javascript Bundling이 필요없이 완성된 HTML을 내어줍니다.
이로 인해, 화면이 초기 랜더링되는 속도가 비약적으로 빨라집니다. Javascript Bundle Size가 Zero이기 때문입니다.
하지만 여기에는 서버 컴포넌트의 제약으로 인해 브라우저에서 일어나는 인터렉션에 반응할 수 없습니다.
이 때 인터렉션을 가능하게 해주는 키워드가 use client 입니다.
이 의미는 이 컴포넌트 부터는 서버에서 끝내지 말고, 브라우저로 Javascript를 보내서 기능을 살려달라고 선언하는 것입니다.
import 문보다 가장 최상단에 use client 문을 작성해 클라이언트 컴포넌트임을 지시할 수 있습니다.
여기서 오해하는 점이 있는데, use client 를 쓴다고 해서 서버 랜더링(프리 페칭)을 아예 안하는 것이 아닙니다.

이후 브라우저에서 Javascript가 연결되는 하이드레이션(Hydration)이 일어나면서 기능이 활성화됩니다.
또한, use client는 파일 단위로 적용되며, 이 파일이 임포트하는 모든 자식 컴포넌트는 클라이언트 컴포넌트 취급입니다.
이를 클라이언트 경계라고 부릅니다.
즉, 한번 클라이언트 영역(경계)로 포함되면, 그 하위의 컴포넌트들도, 함께 브라우저 번들에 포함되는 것입니다.

그러면 서버 컴포넌트와 클라이언트 컴포넌트의 사용 시점은 어떻게 될까요?
우선, 서버 컴포넌트는 데이터베이스에 직접 접근하거나, 민감한 정보를 다룰 때 혹은
사용자와 상호작용 없이 단순히 데이터를 보여주기만 하는 UI 일 때,
혹은 서버에 있는 무거운 라이브러리를 통해 결과를 보여줄 때 주로 사용됩니다.
반대로, 클라이언트 컴포넌트는 이벤트 리스너가 필요할 때, React Hook을 사용할 때,
혹은 브라우저 전용 API를 사용해야 할 경우 사용됩니다.
실무에서는 보통 페이지의 껍데기나 레이아웃은 서버 컴포넌트로 두고,
그 안에 들어가는 상호작용이 필요한 요소를 분리해서 사용하는 것이 보통의 패턴입니다.
이를 나뭇잎 컴포넌트 패턴이라고 명명합니다.

합성 컴포넌트 패턴 - 서버 + 클라이언트
위에서 서버 컴포넌트 안에서 클라이언트 컴포넌트를 불러왔을 때 클라이언트 컴포넌트임을 인지하고,
똑같이 프리 랜더링해 HTML 파일을 브라우저로 내려주고, 브라우저는 JS Bundle 파일을 다운받아
하이드레이션(Hydration) 과정을 거쳐 인터렉티브한 애플리케이션이 완성된다는 것을 배웠습니다.
그러면 반대로 클라이언트 컴포넌트 안에 서버 컴포넌트를 넣고 싶다면 어떻게 해야할까요?
하지만 여기서 문제는 Next.js는 클라이언트 컴포넌트에서 import한 컴포넌트는 모든 클라이언트 컴포넌트로 간주합니다.

이러면서, 서버에서 돌아야 할 DB 로직이나 보안 코드가 브라우저로 전송되는 사태가 발생하는 것입니다.
그러면 이 상황에서 클라이언트 컴포넌트의 기능은 유지하면서 그 안에 서버 컴포넌트를 넣을 수 있는 방법은 뭘까요?
정답은 Children Props를 이용한 컴포넌트 합성(Composition) 패턴입니다.
즉 클라이언트 컴포넌트인 A 컴포넌트에서 자식 컴포넌트를 직접 import 하지 않고, children으로 비워두는 것입니다.

이렇게 되면 A 컴포넌트는 자식으로 들어올 서버 컴포넌트를 직접 알지 못하고, 그저 전달받은 컴포넌트를 화면에 보여줍니다.
children으로 전달된 내용은 서버 컴포넌트일지라도, 이미 서버에서 전달된 결과물인 HTML로 채워지게 됩니다.
이제 부모 컴포넌트인 page.tsx에서 둘을 합쳐주면 완성입니다.
이 패턴은 ContextAPI 같이 Provider를 사용하여 처리할 때 효과적입니다.