1. Next.js의 서버 컴포넌트와 클라이언트 컴포넌트
서버 컴포넌트
서버 컴포넌트란 React에서 선택적으로 서버에 캐시하여 렌더링할 수 있는 컴포넌트다. Next.js에서는 라우트 세그먼트별로 세분화하여 부분 렌더링과 스트리밍이 가능하도록 설계되어 있다.
Next.js에서 서버 컴포넌트를 렌더링하는 전략은 다음 세 가지가 있다.
- 정적 렌더링: 라우트가 빌드 타임에 미리 렌더링되거나 백그라운드에서 재검증된다.
- 동적 렌더링: 라우트가 사용자의 각 요청 타임에 맞춰 렌더링된다.
- 스트리밍: 서버에서 생성된 UI가 준비되는 대로 점진적으로 클라이언트로 전송된다. 이를 통해 사용자는 전체 콘텐츠가 로딩되기 전에 일부 콘텐츠를 미리 볼 수 있다. (Next.js의 앱 라우터 기본값)
서버컴포넌트의 장점 :
- 클라이언트 번들 크기 감소 : 서버에서 렌더링되므로, 브라우저로 전송되는 자바스크립트 양이 줄어들어 초기 로딩 속도 향상 및 성능 개선에 유리하다.
- 데이터 패칭 효율성 : 서버에서 직접 DB나 API 호출 가능해서 클라이언트에서 네트워크 요청을 줄여 속도와 사용자 경험 향상된다.
- 보안성 증가 : 서버에서만 실행되는 로직에 있는 민감한 데이터나 API 키는 클라이언트에 노출되지 않고 안전하게 포함된다.
- 렌더링 성능 최적화 : 클라이언트와 서버 작업을 분리하여 리소스 사용을 효율적으로 분산, 사용자에게 더 빠른 응답 제공이 가능하다
- 코드 스플리팅 자동화 : 필요한 부분만 클라이언트에 전달되므로, 더 빠르고 가벼운 서비스가 가능해진다.
클라이언트 컴포넌트
클라이언트 컴포넌트는 서버에서 미리 렌더링된 인터랙티브 UI다.
브라우저가 JavaScript를 통해 추가 작업을 수행할 수 있게 한다.
클라이언트 컴포넌트의 장점:
- Interactivity: useState, useEffect, 이벤트 리스너 등을 사용하여 사용자와 상호작용하고, UI를 동적으로 업데이트할 수 있다.
- Browser APIs: window, localStorage 등 브라우저 API에 접근 가능하다.
클라이언트 컴포넌트 사용법: 최상단에 'use client'를 선언하여 서버 컴포넌트와 클라이언트 컴포넌트 간의 경계를 설정한다. 'use client'가 선언된 컴포넌트 및 그 하위 자식은 모두 클라이언트 컴포넌트로 간주된다.
Next.js는 효과적인 사용을 위해 다음과 같은 패턴을 추천한다.
- 클라이언트 컴포넌트를 가능한 하단에 배치 클라이언트 JavaScript 번들 크기를 최소화하기 위해 트리의 가능한 하단에 클라이언트 컴포넌트를 배치한다.
// SearchBar는 클라이언트 컴포넌트
import SearchBar from './searchbar'
// Logo는 서버 컴포넌트
import Logo from './logo'
// 기본적으로 Layout은 서버 컴포넌트
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<nav>
<Logo />
<SearchBar />
</nav>
<main>{children}</main>
</>
)
}
예를 들어, 로고 같은 정적 컴포넌트와 검색창 같은 동적 컴포넌트를 분리하여 서버와 클라이언트 각각에 최적화하여 사용한다.
2. 언제 클라이언트 컴포넌트를 쓰고 언제 서버컴포넌트를 써야 할까?
이를 다루기 위해 Next.js는 몇가지 패턴을 추천한다.
작업 내용 | 서버 컴포넌트 | 클라이언트 컴포넌트 |
데이터 가져오기(fetch) | ✅ | ❌ |
백엔드 리소스에 직접 접근 | ✅ | ❌ |
민감한 정보(액세스 토큰, API 키 등)를 서버에 유지 | ✅ | ❌ |
큰 의존성을 서버에 유지하거나 클라이언트 자바스크립트를 줄이기 | ✅ | ❌ |
상호작용 및 이벤트 리스너 추가 (onClick(), onChange() 등) | ❌ | ✅ |
상태 및 라이프사이클 이펙트 사용 (useState(), useReducer(), useEffect() 등) | ❌ | ✅ |
브라우저 전용 API 사용 | ❌ | ✅ |
상태, 이펙트 또는 브라우저 전용 API에 의존하는 커스텀 훅 사용 | ❌ | ✅ |
React 클래스 컴포넌트 사용 | ❌ | ✅ |
3. 서버 컴포넌트와 클라이언트 컴포넌트 함께 사용하기 (Interleaving)
- 서버 컴포넌트는 클라이언트 컴포넌트의 자식이 될 수 없다.
- props로 서버 컴포넌트를 전달하는 것은 가능하다.
불가능한 경우
'use client'
// 클라이언트 컴포넌트에서 서버 컴포넌트를 직접 import할 수 없다.
import ServerComponent from './Server-Component'
export default function ClientComponent({ children }: { children: React.ReactNode }) {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
<ServerComponent /> {/* 불가능 */}
</>
)
}
가능한 경우
'use client'
export default function ClientComponent({ children }: { children: React.ReactNode }) {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
{children} {/* 가능 */}
</>
)
}
클라이언트 컴포넌트는 children이 서버컴포넌트가 될 줄 모르기 때문에 받을 수 있다. 이런 구조에서 결국 ClientComponent는 오직 자식이 어디에 위치하게 될지만 결정하면 되기 때문이다.
그러므로, 아래와 같은 패턴이 가능해진다.
import ClientComponent from './client-component'
import ServerComponent from './server-component'
// Next.js의 페이지는 기본적으로 서버 컴포넌트
export default function Page() {
return (
<ClientComponent>
<ServerComponent />
</ClientComponent>
)
}
4. 서버와 클라이언트 컴포넌트를 왜 분리할까
1. 코드 스플리팅을 통한 성능 개선
- 서버 컴포넌트는 빌드 단계에서 클라이언트 코드와 분리되므로 클라이언트에 불필요한 JavaScript가 전달되지 않는다.
- 이를 통해 클라이언트가 로딩해야 할 JavaScript 크기가 감소하여 페이지의 초기 로딩 속도가 향상된다
2. 클라이언트 측 JavaScript 용량 축소
- 클라이언트에서 실행해야 할 JavaScript의 양이 줄어들어, 사용자의 브라우저 성능이 개선되고 페이지 응답성이 좋아진다.
3. 데이터 로딩 지연에 대한 사용자 경험 개선
- 서버 및 클라이언트 각각에서 Next.js의 Suspense를 활용하여, 데이터가 준비되지 않은 상태에서의 로딩 처리를 자연스럽게 구현할 수 있다.
- 이를 통해 데이터 로딩 중에도 사용자에게 자연스럽고 직관적인 로딩 경험을 제공한다.
4. 초기 로딩 속도의 최적화
- 서버 컴포넌트가 먼저 렌더링 가능한 부분을 빠르게 처리하여 사용자가 더 빠르게 페이지 콘텐츠를 볼 수 있도록 한다
- 이후 상호작용이 필요한 클라이언트 컴포넌트는 비동기 지연 로딩 방식으로 처리하여, 전체적인 로딩 경험을 최적화한다.
5. 마치며
Next.js 프로젝트를 리팩터링 하기 전에 서버와 클라이언트 컴포넌트를 더 명확히 구분해 보았다.
공식 문서를 읽으며 제대로 정리하니 이미 사용하고 있던 패턴도 있고, 새로운 패턴도 있었다. (역시 공식문서가 최고인 것 같다...)
다음 시간엔 서버컴포넌트와 클라이언트를 나눠 리팩토링 할 부분이 남아있는지 찾아보며 Next.js 프로젝트를 성능최적화를 해보겠다.
0. Ref
How are Server Components rendered?
'Develog > Study' 카테고리의 다른 글
Node.js 패키지 매니저 선택(npm& yarn & pnpm 비교) (0) | 2025.04.20 |
---|---|
React의 state는 동기인가 비동기인가. with 이벤트 루프 (0) | 2025.04.17 |
Cursor 사용 후기: 초보자도 웹사이트 만들기 가능한 AI 코드 에디터 체험기 (2) | 2025.04.06 |
프론트엔드 에러 핸들링 구조 정리 (Axios + CustomError 적용) (2) | 2025.04.05 |
Next.js 보일러 플레이트 만들기 (0) | 2025.03.31 |