이번 프로젝트에서 가장 특징적이라고 할만한 부분은 Next.js를 사용해 기존 CSR 방식에서 SSR 방식으로 프로젝트를 구현한다는 것이라고 생각한다. Next.js가 React 프레임워크라고 하고 React를 써봤다면 금방 적응할 수 있다라는 얘기를 많이 들었어서 프론트팀원 모두가 Next.js를 써본 적은 없지만... 금방 적응할 수 있겠지?라는 생각과 요즘은 Next.js를 경험해본 사람을 많이들 선호하기 때문에 이번에는 꼭..! 해야한다라는 마음으로 Next.js를 도입하게 되었다.

하지만 Next.js 자체가 워낙 빠르게 변화하고 있어서 레퍼런스로 참고한 글들이 이전 버전일 때가 많아서 초반 적응에 시간이 많이 걸렸다. 현재까지도 완벽하게 적응을 못하고 있는 것도 사실.
Next.js 강의로 코드잇을 들었는데 강의 자체가 지금과 많이 달라서 2배로 헷갈렸던 것 같다. 그래서 오늘 최신 정보들을 싹싹 다 긁어와서 확실하게 정리하고 넘어가기로 했다. 생각보다 정리해야 하는 것들이 많은 것 같아 오늘은 가장 핵심인 App Router만을 정리한다.
※ 지금 작성하는 시점의 Next.js 버전은 14.2.5이다.
13버전을 기점으로 Next.js가 많이 변화했다고 한다. 필자는 14버전부터 사용했기 때문에 13버전 이전의 내용은 다루지 않을 예정이다.
Next.js에서는 react-router-dom 없이 Automatic routing이 가능하도록 하는 파일시스템 기반 라우팅을 위한 pages 디렉토리가 있었지만, 13버전에서 라우팅 방식을 향상시킨 app 폴더가 추가로 생겼다. 또한, 페이지 단위로 렌더링 방식을 규정하지 않고 서버 컴포넌트, 클라이언트 컴포넌트가 도입이 되었다. app 폴더 안에 있는 모든 컴포넌트는 기본적으로 서버 컴포넌트이다. 서버 컴포넌트와 클라이언트 컴포넌트는 실행 되는 환경이 다르기에 서로 역할이 다르다.

프로젝트 내부에 위치한 컴포넌트는 기본적으로 서버 컴포넌트로 동작한다.
기존 SSR 방식은 사용자가 페이지를 요청하면, 서버가 데이터베이스에서 데이터를 가져와 HTML 파일을 만들고, 브라우저에 전달한다. 브라우저는 이 HTML을 그대로 렌더링해 화면에 표시하는 방식으로 동작했다. 하지만 서버 컴포넌트는 사용자가 페이지를 요청하면, 서버가 데이터베이스에서 데이터를 가져와 리액트 컴포넌트를 생성하고, 그 컴포넌트의 결과(트리 구조)를 브라우저로 전달한다. 브라우저는 이 결과를 화면에 표시하는 방식으로 동작하게 된다. 이를 통해 보안에 민감한 정보에 안전하게 접근하고 렌더링에 필요한 라이브러리를 번들에 포함하지 않음으로써 JS의 사이즈를 줄일 수 있게 된다.
서버에서 동작하는 컴포넌트로 useEffect, useState 등의 훅을 사용할 수 없다.
전체 어플리케이션을 클라이언트에서 렌더링하는 것이 아닌 의도에따라 어디에서 컴포넌트를 렌더링할지 정할 수 있다. 예를 들어 위 그림에서 Navbar, Sidebar, Main 컴포넌트는 interactive한 부분이 없는 정적인 컴포넌트이기 때문에 서버에서 Server component로 렌더링할 수 있다. 나머지 interactive한 UI는 Client component로 렌더링하게 된다. 이를 Next.js에선 Server-first 접근법이라 한다.
use client로 선언해야 한다. use client 선언의 의미는 서버에서 렌더링되고, 클라이언트에서 hydration 작업이 수행됨을 의미한다.Hydration은 서버에서 렌더링된 정적 HTML을 클라이언트에서 인터랙티브하게 만드는 과정입니다. Next.js가 서버에서 HTML을 생성해 보내고 나면, 클라이언트에서 이 HTML을 받아 React 코드를 다시 실행합니다. 이 과정에서 서버에서 받은 HTML을 기반으로 컴포넌트를 재구성하고, 이 컴포넌트에 이벤트 핸들러(클릭, 입력 등)나 상태 관리를 추가하여 페이지를 인터랙티브하게 만든다. 이를 통해 사용자는 페이지가 빠르게 로드되면서도 React의 다양한 인터랙티브 기능을 이용할 수 있다.
클라이언트 컴포넌트 내부에서는 서버 컴포넌트를 사용할 수 없다. (단, children의 형태로 서버 컴포넌트를 사용할 수 있다.)
useState, useEffect 등 hook을 사용할 때, 브라우저 API를 사용할 때와 같은 상황에서 클라이언트 컴포넌트를 사용한다.
Client component는 기본적으로 가능하다면 트리상에서 리프 노드에 두어야 한다. Client component가 되고 나선 그 아래부턴 모두 Client component가 되기 때문에 사용할 곳을 최소화 하기 위함이다. 따라서 interactive한 UI만을 리프노드로써 client component로 만들어야 한다.
app 폴더 최상위 page.js는 사용자가 서비스에 접근 할 때 가장 처음으로 보는 페이지이다. 만약 이곳에서 사용자의 이벤트를 처리하거나 hooks를 사용해야 한다면, 즉 app 폴더 최상위 page.js 를 클라이언트 컴포넌트로 만들면 Next.js 를 사용하는 의미가 없다. -> 작은 단위로 컴포넌트를 만들어나가는 것이 더 중요해졌다.
Server에서 Client로 전달하는 props는 serilization(데이터 직렬화)을 해야 한다고 한다. Date, 함수 등의 값들은 직접적으로 전달할 수 없다. 따라서 직렬화할 수 없는 데이터를 전달하려면, 먼저 직렬화 가능한 형태(예: 문자열, JSON 객체)로 변환해야 합니다.
third-party 패키지들은 클라이언에서만 사용할 수 있는 feature를 사용한다면 use client 문을 상단에 추가해야 한다. Server component 내에서는 정상적으로 동작 안 할 수 가 있다. 그래서 이런 경우에는 third-party 컴포넌트를 Client component로 만들어서 사용하면 된다.
기존 Pages Router에서는 정적인 공통 마크업은 _document에 작성하고, 모든 페이지가 공유하는 로직은 _app에 작성했다. App Router 방식부터는 해당 파일이 사라지고 디렉토리 단위로 적용되는 layout 개념이 생기게 되었다. 파일 경로와 이름에 따라 사이트 주소가 설정되는 규칙 또한, 디렉토리 구조로 경로를 구분하고 파일은 page라는 이름을 갖도록 변경되었다.

app 이라는 폴더 아래 dashobard, settings 라는 폴더를 만들면 URL path가 폴더 이름과 동일한 순서를 따라 만들어진다. 결국 폴더를 중첩하면 하나의 route를 생성할 수 있다.

Component Hierarchy는 아래와 같다.

형제 관계인 route 사이에서의 이동시에 next.js는 변화하는 route안의 page와 layout만을 fetch하고 렌더링 한다.

layout은 여러 페이지 사이에서 공유하는 UI이다. route 이동시 layout은 상태를 보존하고, interactive한 상태로 남아있는다. 또한 리렌더도 하지 않는다.
레이아웃을 만들려면 layout.js파일을 만들면 된다. layout.js는 child layout이나 page를 감싸기 위해 children props를 설정해주어야 한다.
가장 상단의 레이아웃을 root layout이라 한다. 이 layout은 어플리케이션 전체에서 공유된다. root layout은 html과 body 태그를 가져야 한다.
어떤 route segment든 layout을 정의할 수 있다.
route내의 layout은 default로 중첩된다. 각 부모 레이아웃은 자식 레이아웃을 감싸는 형태로 되어있다.

layout도 data fetching이 가능하다.
부모와 자식 layout간 데이터 전달은 불가능하지만 같은 데이터를 route에서 한번 더 요청하면 React가 자동적으로 성능에 영향이 가지않게 중복 요청을 제거해준다고 한다.
레이아웃은 현재 route segment에 접근할 수 없다. (Layout 내에서 현재 페이지나 라우트에 대한 정보를 바로 얻을 수 없다는 뜻) 접근할려면 useSelectedLayoutSegement 나 useSelectedLayoutSegements 훅을 client component에서 사용할 수 있다.
useSelectedLayoutSegment: 현재 활성화된 단일 경로 세그먼트를 반환한다. 예를 들어, /about/team 경로에서 team 세그먼트를 반환한다.
useSelectedLayoutSegments: 모든 활성화된 경로 세그먼트를 배열 형태로 반환한다. 예를 들어, /about/team 경로에서는 ['about', 'team']을 반환한다.
app 폴더의 계층은 URL 경로로 직접 매핑된다. 하지만 route group을 생성하면 이러한 패턴을 벗어날 수 있다. route group을 사용할려면 폴더명을 괄호 ()로 묶는다. ex) (folderName)
1. URL 구조에 영향을 주지않고 라우트를 조직화할 때

(marketing)과 (shop)이 같은 URL 계층을 공유하고 있지만 route group 폴더에 layout.js 파일을 생성해 각기 다른 layout을 적용할 수 있다.

2. 특정 route segment를 레이아웃에 삽입할 때
특정한 라우트를 레이아웃으로 선택하려면 새 route group을 생성하고 같은 레이아웃을 그룹에서 공유하게 한다. group밖의 route는 레이아웃을 공유하지 않을 것이다.

3. 어플리케이션을 분할해서 다수의 root layout을 생성할 때
root layout을 여러개 생성하려면 루트의 layout.js 파일을 삭제하고 각각의 route group 폴더에 layout.js 파일을 생성한다. 이렇게 하면 어플리케이션을 완전히 다른 UI와 경험을 가지는 영역으로 쪼갤 수 있다. 각 root layout은 html, body 태그를 정의해주어야 한다.

이 때 multiple root layout 간 이동은 full page reload가 적용된다.
미리 정확한 route segment명을 알 수 없고 동적인 데이터로 route를 생성하고 싶을 때 Dynamic segments를 사용할 수 있다.
Dynamic segment는 폴더명을 square 괄호 [] 로 감싸서 생성할 수 있다. ex) [id]
dynamic segment는 params prop으로 layout, page, route와 generateMetadata 함수에 전달된다.
_folder 식으로 작성된 폴더는 route로 생성되지 않는다.

기본 메타데이터는 layout과 page 파일에서 사용할 수 있다.
import { Metadata } from 'next';
export const metadata: Metadata = {
title: '...',
description: '...',
};
동적인 페이지에서는 generateMetadata 함수를 이용하여 메타데이터를 생성한다.
import { Metadata } from 'next';
import { getAllPost } from 'lib/posts';
type Props = {
params: { id: string };
};
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getAllPost(params.id); // post 정보를 가져옴
return {
title: post.title,
description: post.description,
};
}
export default function Page({ params }: Props) {
return <div>...</div>;
}
title: 페이지의 제목으로, 브라우저 탭에 표시
description: 페이지의 설명으로, 검색 엔진과 소셜 미디어에서 사용
keywords: 페이지와 관련된 키워드 목록
openGraph: Open Graph 메타데이터로, 소셜 미디어에서 링크를 공유할 때 사용
twitter: Twitter 카드 메타데이터로, 트위터에서 링크를 공유할 때 사용
default : title 속성이 정의되지 않은 페이지가 있을 경우 default 속성을 사용
template : title 속성을 동적으로 설정
absolute : 하위 페이지에서 사용. 상위 페이지의 title은 무시되고 하위 페이지의 title을 설정

누가 Next.js React만 쓸 줄 알면 가능하다고 누가 말했는가... 공부해야 할 게 굉장히 많고 많은 것 같다. 나중에 몇 가지 더 정리해야 할 게 많고 적응해야 할 게 많다. 프로젝트 글을 쓰려고 했는데 해당 내용을 아직 적용하고 수정하는 과정이라 아마 추후에 폴더/컴포넌트 정리를 한 다음에 정리하지 않을까 싶다 ㅎㅎㅎ...
[Frontend] React와 Next.js의 차이? / Next.js의 필요성 / Next.js 13버전 변경점
[Next.js 14] 서버 컴포넌트 / 클라이언트 컴포넌트
Next.js app router 공식문서 정리
Next.js - 서버 컴포넌트 & 클라이언트 컴포넌트
Next.js 14 맛보기
Next.js 14 업데이트 살펴보기
Next js 14+ SEO 설정하기 - Metadata title, description 설정방법