안녕하세요, 웹파트 OB 공준혁입니다.
저번 세미나 때는 API 통신과 타입스크립트에 대해 배웠는데, 다음 단계로 나아갈 수도 있는 Next.js를 배워보고자 아티클을 작성해봤습니다 ! 따라서 오늘은 Next.js 의 전체적인 개념에 대해 톺아보는 시간을 갖도록 하겠습니다. (중요한 부분 위주로!) 참고로, Next.js는 크게 page router 방식과 app router 방식이 있는데, 최근 방식인 app router 기준으로 설명하겠습니다.

Next.js 는 프레임워크 입니다. React.js 는 라이브러리죠.
React 라이브러리를 사용하여, 더욱 개발자에게 편리성을 부여해주면서 프레임워크인 Next.js 가 탄생했습니다.
프레임워크는 라이브러리에 비해 자유도는 낮습니다. 즉, 정해준 규칙을 반드시 따라야만 하는 부분이 있습니다.
(그건 리액트 문법도 그렇지 않나요? 라고 물으신다면... Next.js 에서는 라우팅을 위한 규칙(파일명, 폴더 위치), SEO를 위한 변수명 규칙 등등이 존재합니다. 따라서 프레임워크가 당연히 더욱 자유도를 제한할 수 밖에 없는 구조입니다. )
Routing
리액트에서는 route 를 위한 하나의 컴포넌트(혹은 페이지)를 만든 후, 거기서 path에 따라 다른 컴포넌트가 렌더링되도록 구현되어 있었습니다. 근데 코드로만 보면 결국 모든 페이지를 import하는 것이므로, 라우팅이 꽤 많으면... 로딩도 시간이 꽤 걸리곤 했었죠.
Next.js 에서는 페이지별로 코드 스플리팅이 된다고 했습니다. 그리고 Next.js에서 라우팅을 구현하기 위해서는
여러가지 규칙들을 지켜서 폴더, 파일을 만들어야 합니다. 우선 폴더 구조를 지켜야 합니다.
src/
├── app/
│ ├── layout.tsx // 최상위 레이아웃
│ ├── page.tsx // 홈 페이지
│ ├── dashboard/ // 중첩된 라우팅 폴더
│ │ ├── layout.tsx // 대시보드 레이아웃
│ │ ├── page.tsx // 대시보드 메인 페이지
│ │ └── [userId]/ // 다이나믹 라우팅 폴더 (필요하면 찾아보세요!)
│ │ ├── layout.tsx // 유저 페이지 레이아웃
│ │ └── page.tsx // 특정 유저 페이지
│ └── settings/ // 추가 라우팅 폴더
│ ├── layout.tsx // 설정 레이아웃
│ └── page.tsx // 설정 메인 페이지
└── components/ // 공통 컴포넌트들
└── Header.tsx
폴더 구조의 예시를 하나 들어봤습니다. 참고로, 하나의 폴더 (app 폴더 안에 존재하는)는 segment라고 불립니다.
이 예시를 기반으로 기본적인 Routing rule을 설명해보겠습니다. (app router의 경우)
- 반드시 app/ 폴더 안에 page파일이 위치해야만 합니다.
- Routing 할 수 있는 주소는 폴더명으로 정해지며,
해당 주소에서 렌더링될 페이지는 page.tsx (혹은 .jsx, .js 등) 의 이름을 가진 파일로 작성되어야 합니다. - segment 내에서 전체적으로 적용하고 싶은 레이아웃이 있다면, layout.tsx 라는 이름으로 파일을 만들어야 합니다.
- 레이아웃은 중첩될 수 있습니다.
만약 아래와 같은 링크로 접속하게 된다면,
localhost:3000/dashboard/3
위의 폴더 구조에서 app/layout.tsx 와 app/dashboard/layout.tsx 와 app/dashboard/[userId]/layout.tsx 가 적용된 후
app/dashboard/[userId]/page.tsx 가 렌더링되게 됩니다.

공식 문서에서 안내해주고 있는 파일 규칙들입니다. 라우팅에 대한 기본적인 이해는 여기서 끝내겠습니다.
사실 여기서 끝내기엔 아쉽습니다. 이전 버전이었던 page router 방식에서는 클라이언트에서 렌더링되었는데
app router가 적용된 이후, 모든 컴포넌트는 기본적으로 server component 가 되었습니다.
그래서 client component와 server component의 이해도 가져가봅시다.
Client Component, Server Component
Client Component는 클라이언트 측에서 인터랙티브한 UI를 렌더링하는 데 사용됩니다.
브라우저 API, 이벤트 리스너, useState 등을 사용하려면 전부 클라이언트 측에서 컴포넌트가 렌더링되어야 합니다.
반면 Server Component는 서버에서 렌더링해두는 컴포넌트라고 생각하시면 됩니다.
서버에서 UI를 렌더링한 뒤 이를 클라이언트 측에 스트리밍하므로 만약 동일한 컴포넌트가 재요청된다면 캐싱해둔 컴포넌트를 반환하면 되므로 더욱 빠른 렌더링이 가능합니다. 뿐만 아니라, 만약 프론트 배포 환경과 백 배포 환경이 같을 경우 데이터 패칭도 서버 컴포넌트에서 내부적으로 수행하여 보안성도 챙기고, 빠른 속도로 데이터를 가져올 수 있습니다.
기본적으로 app router 방식에서는 모든 컴포넌트가 server component라고 했는데요,
따라서 평소에 사용하는 useState나 localStorage 접속이나 addEvent를 하려면 특별한 지시어를 사용하여
해당 컴포넌트가 Client Component 임을 알려야 합니다.
"use client";
// src/components/Counter.tsx
"use client";
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Current Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
해당 컴포넌트가 정의된 파일 맨 윗 부분에 "use client"; 지시어를 사용하여 클라이언트 컴포넌트임을 명시합니다.
그래야 client component 임을 알고 빌드 에러도 안내고, 클라이언트에서 정상적으로 렌더링을 해주게 됩니다.
서로 포함 관계 가능 여부
우선 당연히 같은 종류의 컴포넌트끼리는 포함이 가능함을 알립니다.
server component ⊃ client component : ⭕(가능)
client component ⊃ server component : ❌ (불가능)
client component 내에 포함된 컴포넌트는 전부 client component 로 인식됩니다.
따라서 client component의 자식 컴포넌트들은 전부 서버가 아닌 클라이언트에서 렌더링됩니다.
이는 자식 컴포넌트 내에서 "use client"; 지시어를 사용하지 않아도 됨을 뜻하긴 합니다. 다만 자식 컴포넌트 내에서도 브라우저에서 제공하는 기능들을 사용하고 싶다면 "use client"; 를 반드시 써주어야 합니다. (가독성의 이유로 그냥 써주기도 합니다.)
렌더링 과정 ( + Client Component와 Server Component가 엮였을 때)
Client Component 의 렌더링 과정
1. 서버에서 생성해준 정적 HTML을 받아 초기 페이지 로딩
2. 동시에 서버로부터 Javascript 파일 다운로드
3. Hydrate 과정을 거쳐 DOM에 이벤트를 연결 (Javascript 파일을 연결하는 과정)
위의 과정을 통해 클라이언트 컴포넌트가 상호 작용이 가능해지게 됩니다. 이후 navigation 이벤트가 발생 시,
CSR에 따라 클라이언트에서 JS를 실행하여 컴포넌트를 렌더링하게 됩니다.
즉, 초기 렌더링 이후에는 모든 리렌더링이나 새로운 렌더링은 클라이언트에서 처리 됩니다.
- 초기 HTML 렌더링: 서버는 초기 HTML을 생성하고 클라이언트에 전달합니다. 이 HTML은 정적 콘텐츠만 포함되어 있으며, 아직 자바스크립트 이벤트가 동작하지 않는 상태입니다. 이는 사용자에게 초기 로딩 속도를 빠르게 제공하고, SEO 최적화에도 도움이 됩니다.
- 클라이언트 자바스크립트 로딩: 클라이언트는 서버가 보낸 HTML을 화면에 표시한 뒤, 해당 컴포넌트와 페이지에 필요한 자바스크립트 파일을 로딩합니다.
- 이벤트 바인딩과 상태 복구: 자바스크립트가 로딩되면, React가 서버에서 렌더링된 HTML과 클라이언트 React 컴포넌트의 가상 DOM을 비교하여 차이점을 확인하고, 필요한 이벤트 핸들러와 상태를 HTML 요소에 바인딩합니다. 이를 통해 HTML이 인터랙티브하게 변하며, 사용자 입력에 반응할 수 있게 됩니다.
Server Component 의 렌더링 과정
1. Next.js는 React API를 사용하여 서버에서 HTML을 생성
2. 이를 React Server Component Payload(RSC Payload) 라는 데이터 형식으로 보냄.
(RSC Payload는 Client component에 대한 참조를 포함한 형식)
3. 서버 스트리밍 과정을 거칩니다.
서버 스트리밍 과정?
RSC Payload를 사용하여 클라이언트와 서버 컴포넌트 트리를 동기화하고,
클라이언트에서는 상호작용이 필요한 JavaScript만 추후 수분화(hydrate)됩니다.
이런 과정을 통해 클라이언트가 빠르게 초기 화면을 볼 수 있도록 돕습니다.
위에서 서버에서 HTML을 생성한다고 했는데, 그 렌더링 자체를 더욱 자세히 나눌 수 있습니다.
서버 렌더링은 크게 정적 렌더링, 동적 렌더링, 스트리밍의 세 가지로 나뉩니다.
- 정적 렌더링: 빌드 시점에 미리 HTML을 생성하고, CDN에 캐시하여 빠르게 페이지를 로드합니다.
- 동적 렌더링: 사용자별로 데이터를 요청할 필요가 있을 때 요청 시마다 서버에서 렌더링합니다.
- 스트리밍: 각 페이지의 일부를 쪼개어 비동기 스트리밍 방식으로 클라이언트에 전달합니다. 이를 통해 일부 UI를 사용자에게 빠르게 보여줄 수 있습니다.
Server Component 안에 Client Component 이 존재할 경우 렌더링 과정
사실 이 렌더링 과정은 위의 Server Component 렌더링 과정만 명확히 이해했다면 문제 없이 이해할 수 있습니다.
1. Server Component를 렌더링하여 HTML을 생성
2. 만약 Client Component를 만난다면, 이는 클라이언트 측에서 렌더링 해야하므로 서버에서는 HTML로 변환 X
3. 단지 Client Component의 부분을 플레이스홀더로 처리 !
플레이스홀더로 처리된다는 의미는, 렌더링 되진 않지만 자리를 차지한 상태로 HTML 이 생성된다는 의미입니다.
예시 코드를 봅시다.
// app/page.tsx (Server Component)
import ClientButton from './ClientButton';
export default function Page() {
return (
<div>
<h1>Server-rendered Title</h1>
<ClientButton />
</div>
);
}
// app/ClientButton.tsx (Client Component)
'use client';
import { useState } from 'react';
export default function ClientButton() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
You clicked {count} times
</button>
);
}
이 예시 코드는 Server Component 안에 Client Component가 존재하는 경우입니다.
이런 경우에는, 서버에서 렌더링 될 때
<div>
<h1>Server-rendered Title</h1>
<!-- ClientButton 자리. 자바스크립트가 로드되면 이 자리에 렌더링 -->
</div>
이런 형식으로 인식하고 렌더링해서 클라이언트에 보내주고,
자바스크립트가 로드된 후 클라이언트 측에서 Hydrate 과정을 거쳐 완전히 렌더링되게 됩니다.
결국 서버 컴포넌트 부분은 서버에서 렌더링해주고, 클라이언트 컴포넌트 부분은 클라이언트 부분에 미뤄두었다가
클라이언트 컴포넌트가 추후 렌더링한다고 이해해도 좋습니다 !
이렇게 해서 Next.js 의 핵심 기능과 컨벤션들, 렌더링 과정에 대해 살펴봤습니다.
이제 Next.js를 도입해보고 필요할 때 찾아보며 덧붙여 나가봅시다 !
출처: https://ocahs.tistory.com/4 [ocahs 개발 블로그:티스토리]
Next.js 톺아보기
안녕하세요, 오늘은 Next.js 의 전체적인 개념에 대해 톺아보는 시간을 갖도록 하겠습니다.참고로, Next.js는 크게 page router 방식과 app router 방식이 있는데, 최근 방식인 app router 기준으로 설명하겠
ocahs.tistory.com
'4주차' 카테고리의 다른 글
React Router에서 Outlet과 Layout을 활용하는 방법 (0) | 2024.11.12 |
---|---|
Protected Route로 라우트 보호하기 (0) | 2024.11.12 |
TypeScript의 .d.ts 파일 🧐 (0) | 2024.11.12 |
타입스크립트의 타입 (0) | 2024.11.12 |
[4주차] Typescript 왜 사용할까? 제대로 알고 사용하자 (0) | 2024.11.12 |