서버와 클라이언트의 차이 이해하기 — Server Components vs Client Components
왜 이 장이 그렇게 중요할까요?
여기서 한 번 크게 숨을 고르고 가야 합니다. Next.js를 배우다 보면 어느 순간 이런 생각이 듭니다.
"분명 React처럼 컴포넌트를 만들고 있는데, 왜 어떤 건 서버고 어떤 건 클라이언트지?"
바로 이 지점이 App Router 시대의 핵심입니다. 이걸 제대로 이해하면 다음 챕터의 데이터 가져오기, 폼 처리, 서버 액션, 캐싱이 줄줄이 쉬워집니다. 반대로 여기서 헷갈리면 코드는 돌아가도 머릿속은 자꾸 미로가 됩니다.
이번 장에서는 어렵게 느껴지는 말을 최대한 일상적인 감각으로 풀어보겠습니다. 결론부터 아주 짧게 말하면 이렇습니다.
서버 컴포넌트는 준비하고 보여주는 일에 강하고, 클라이언트 컴포넌트는 반응하고 움직이는 일에 강합니다.
이 한 문장을 끝까지 붙잡고 가면 됩니다.
먼저, 이름부터 바로잡아 봅시다
초보자가 가장 많이 헷갈리는 이유는 이름 때문입니다. "클라이언트 컴포넌트"라고 하면 브라우저에서만 렌더링되는 것처럼 들리고, "서버 컴포넌트"라고 하면 옛날 SSR과 완전히 같은 것처럼 들립니다.
하지만 Next.js의 App Router에서는 조금 다르게 이해해야 합니다.
서버 컴포넌트는 서버 쪽에서 실행되는 컴포넌트입니다. 데이터 읽기, 비밀 키 다루기, 무거운 작업 처리, 초기 HTML 준비 같은 일에 잘 맞습니다.
클라이언트 컴포넌트는 브라우저에서 상호작용해야 하는 컴포넌트입니다. 버튼 클릭, 입력창 상태 변화, 모달 열기, 탭 전환 같은 일을 맡습니다.
즉, 핵심 기준은 "어디서 보이느냐"보다 "어디서 실행되어야 하느냐"입니다.
비유로 이해하기: 주방과 홀
웹 페이지를 작은 식당이라고 생각해 봅시다.
주방은 서버입니다. 재료를 손질하고, 요리를 만들고, 손님에게 내보낼 준비를 합니다.
홀은 브라우저입니다. 손님이 메뉴를 누르고, 요청을 바꾸고, 추가 주문을 하고, 반응을 확인하는 곳입니다.
서버 컴포넌트는 주방에서 미리 준비해 놓는 접시와 같습니다. 이미 보기 좋게 담겨 나옵니다.
클라이언트 컴포넌트는 홀에서 손님과 상호작용하는 직원과 비슷합니다. "덜 맵게 해 주세요", "이 메뉴 좋아요 눌러 주세요", "정렬을 최신순으로 바꿔 주세요" 같은 반응을 처리합니다.
둘 중 하나만 중요한 게 아닙니다. 좋은 식당이 주방만 좋거나, 홀만 좋아서는 안 되듯이 Next.js 앱도 두 역할이 잘 나뉘어야 편하고 빠릅니다.

Next.js 최신 App Router의 기본값은 서버 컴포넌트입니다
App Router에서는 아무 표시도 하지 않으면 컴포넌트는 기본적으로 서버 컴포넌트입니다.
예를 들어 app/page.tsx나 app/layout.tsx는 보통 서버 컴포넌트로 시작합니다.
export default function Page() {
return <h1>안녕하세요</h1>
}위 코드는 따로 "use client"가 없으므로 서버 컴포넌트입니다.
이 기본값이 꽤 중요합니다. Next.js는 처음부터 "가능하면 서버에서 처리하고, 꼭 필요할 때만 클라이언트로 보내자"라는 철학을 가지고 있기 때문입니다.
쉽게 말해, 기본 전략은 이렇습니다.
먼저 서버에서 최대한 준비한다
정말 상호작용이 필요한 부분만 클라이언트로 보낸다
그래서 브라우저가 내려받아야 할 JavaScript를 줄인다
이 방식 덕분에 초기 화면이 더 빨리 보이고, 성능도 좋아지기 쉽습니다.
서버 컴포넌트는 무엇을 잘할까요?
서버 컴포넌트는 "보여주기 전에 준비해 둘 수 있는 일"에 강합니다.
예를 들어 이런 일들입니다.
데이터베이스나 API에서 데이터 가져오기
비밀 정보가 필요한 작업 처리하기
브라우저로 보낼 JavaScript 양 줄이기
정적인 UI를 빠르게 보여주기
무거운 라이브러리를 서버 쪽에만 두기
우리의 '작은 기록 상점' 프로젝트로 바꿔 보면 더 쉽습니다.
기록 목록 페이지에서 서버 컴포넌트는 이런 일을 맡기 좋습니다.
오늘의 기록 목록 가져오기
각 기록의 제목, 요약, 날짜 렌더링하기
카테고리별 개수 계산하기
로그인한 사용자에 따라 보여줄 기본 데이터 준비하기
이런 일은 사용자가 버튼을 누르기 전에도 미리 준비할 수 있습니다. 그래서 서버 컴포넌트가 잘 어울립니다.
서버 컴포넌트에서는 무엇이 안 될까요?
서버 컴포넌트는 브라우저에서 직접 반응해야 하는 일은 못 합니다.
대표적으로 다음은 사용할 수 없습니다.
onClick,onChange같은 이벤트 핸들러useState,useEffect같은 클라이언트 훅window,document,localStorage같은 브라우저 API
예를 들어 아래 코드는 서버 컴포넌트에서 쓰면 안 됩니다.
export default function Page() {
return (
<button onClick={() => alert('클릭!')}>
누르기
</button>
)
}왜냐하면 클릭은 브라우저에서 일어나는 일인데, 서버 컴포넌트는 그런 상호작용을 직접 처리하지 않기 때문입니다.
즉, 서버 컴포넌트는 "버튼의 모양"은 준비할 수 있어도, "버튼을 눌렀을 때 반응하는 일"은 직접 맡지 않습니다.
클라이언트 컴포넌트는 언제 필요할까요?
사용자와 상호작용하는 순간, 클라이언트 컴포넌트의 차례입니다.
클라이언트 컴포넌트는 파일 맨 위에 "use client"를 붙여서 만듭니다.
'use client'
import { useState } from 'react'
export default function LikeButton() {
const [liked, setLiked] = useState(false)
return (
<button onClick={() => setLiked(!liked)}>
{liked ? '좋아요 취소' : '좋아요'}
</button>
)
}이제 이 컴포넌트는 브라우저에서 상태를 바꾸고, 클릭에 반응할 수 있습니다.
클라이언트 컴포넌트가 필요한 대표 상황은 다음과 같습니다.
클릭, 입력, 드래그처럼 사용자의 행동을 처리할 때
useState,useEffect같은 훅이 필요할 때window,localStorage같은 브라우저 기능이 필요할 때실시간으로 UI가 바뀌어야 할 때
예를 들어 '작은 기록 상점'에서는 이런 요소가 클라이언트 컴포넌트가 되기 쉽습니다.
좋아요 버튼
검색창 입력값
정렬 드롭다운
모달 열기/닫기
탭 전환
아주 중요한 오해 1: 클라이언트 컴포넌트는 처음부터 전부 브라우저에서만 그려질까요?
아닙니다. 이 부분이 정말 많이 헷갈립니다.
클라이언트 컴포넌트라고 해서 무조건 첫 화면부터 브라우저에서만 만들어지는 것은 아닙니다. 초기 페이지 로드에서는 Next.js가 서버에서 미리 HTML을 준비해 빠르게 보여줄 수 있습니다. 그 다음 브라우저에서 JavaScript가 연결되면서 상호작용이 살아납니다. 이 과정을 하이드레이션이라고 합니다.
말이 조금 낯설지만 뜻은 간단합니다.
처음엔 "움직이지 않는 모형"이 보이고, 잠시 뒤 "진짜 버튼"으로 깨어난다고 생각하면 됩니다.
그래서 클라이언트 컴포넌트는 이렇게 이해하는 게 정확합니다.
"브라우저에서 상호작용해야 하는 컴포넌트"
이 정의가 "브라우저에서만 처음부터 끝까지 렌더링되는 컴포넌트"보다 훨씬 정확합니다.
아주 중요한 오해 2: 서버 컴포넌트와 SSR은 같은 말일까요?
비슷하지만 완전히 같은 말은 아닙니다.
SSR은 전통적으로 "서버에서 HTML을 만들어 보내는 방식"에 초점이 있습니다. 반면 서버 컴포넌트는 "어떤 컴포넌트 코드를 브라우저로 보내지 않을 것인가"까지 포함하는 더 세밀한 개념입니다.
초보자 눈높이에서 핵심만 정리하면 이렇습니다.
| 구분 | 핵심 질문 |
|---|---|
| SSR | 이 페이지의 HTML을 서버에서 만들까? |
| Server Components | 이 컴포넌트의 실행 책임을 서버에 둘까? |
즉, 서버 컴포넌트는 단순히 "서버에서 HTML을 만든다"를 넘어서, 클라이언트에 보낼 코드 자체를 줄이는 설계 방식이라고 보면 됩니다.
"use client"는 스위치 하나가 아니라 경계선입니다
"use client"를 붙이면 "이 파일만 클라이언트로"라고 생각하기 쉽습니다. 그런데 실제로는 조금 더 큽니다.
이 파일이 클라이언트 경계의 시작점이 되고, 여기서 가져오는 모듈과 자식 컴포넌트들도 클라이언트 쪽 번들에 포함될 수 있습니다.
그래서 습관처럼 큰 컴포넌트 맨 위에 "use client"를 붙여 버리면, 원래 서버에 남겨 둘 수 있었던 코드까지 한꺼번에 브라우저 쪽으로 넘어갈 수 있습니다.
예를 들어 전체 레이아웃이 대부분 정적이라면 레이아웃 전체를 클라이언트 컴포넌트로 만들기보다, 검색창 하나만 클라이언트 컴포넌트로 분리하는 편이 좋습니다.
import Logo from './logo'
import SearchBox from './search-box'
export default function Header() {
return (
<header className="flex items-center justify-between">
<Logo />
<SearchBox />
</header>
)
}'use client'
import { useState } from 'react'
export default function SearchBox() {
const [keyword, setKeyword] = useState('')
return (
<input
value={keyword}
onChange={(e) => setKeyword(e.target.value)}
placeholder="기록을 검색해 보세요"
/>
)
}이 구조가 좋은 이유는 단순합니다. 로고와 헤더 틀은 서버에 남기고, 진짜 움직여야 하는 검색창만 클라이언트에 보내기 때문입니다.
우리 프로젝트로 바로 보기: '작은 기록 상점'은 어떻게 나눌까요?
이제 실제로 프로젝트를 쪼개 봅시다.
기록 목록 페이지에는 크게 두 종류의 요소가 있습니다.
첫째, 내용을 보여주는 부분입니다. 기록 제목, 설명, 작성일, 카테고리 같은 정보입니다.
둘째, 사용자가 만지는 부분입니다. 좋아요 버튼, 정렬 버튼, 검색창, 필터 탭 같은 요소입니다.
이걸 컴포넌트로 나누면 이런 그림이 됩니다.
기록 목록 페이지 (서버)
├─ 기록 목록 영역 (서버)
│ ├─ 기록 카드 (서버)
│ └─ 기록 카드 (서버)
├─ 검색창 (클라이언트)
├─ 정렬 드롭다운 (클라이언트)
└─ 좋아요 버튼 (클라이언트)이 구조가 Next.js다운 구조입니다. 보여주는 큰 틀은 서버가 맡고, 손이 가는 부분만 클라이언트가 맡습니다.
서버 컴포넌트에서 데이터를 준비하고, 클라이언트 컴포넌트에 넘기기
아래 예제를 보겠습니다.
서버 컴포넌트: 페이지에서 기록 목록 가져오기
import FavoriteButton from '@/app/ui/favorite-button'
async function getRecords() {
return [
{ id: 1, title: '봄비 메모', summary: '우산을 사야겠다고 적어 둔 기록', liked: 3 },
{ id: 2, title: '책갈피 수집', summary: '예쁜 종이 조각을 모으는 취미 기록', liked: 7 },
]
}
export default async function Page() {
const records = await getRecords()
return (
<main className="space-y-4">
<h1 className="text-2xl font-bold">작은 기록 상점</h1>
{records.map((record) => (
<article key={record.id} className="rounded-xl border p-4">
<h2 className="text-lg font-semibold">{record.title}</h2>
<p className="text-sm text-gray-600">{record.summary}</p>
<FavoriteButton initialCount={record.liked} />
</article>
))}
</main>
)
}클라이언트 컴포넌트: 좋아요 버튼 처리하기
'use client'
import { useState } from 'react'
export default function FavoriteButton({ initialCount }: { initialCount: number }) {
const [count, setCount] = useState(initialCount)
return (
<button
className="mt-3 rounded-md bg-black px-3 py-2 text-white"
onClick={() => setCount(count + 1)}
>
좋아요 {count}
</button>
)
}여기서 역할이 아주 선명합니다.
페이지는 서버 컴포넌트입니다. 데이터를 가져오고, 기록 카드 구조를 만듭니다.
FavoriteButton은 클라이언트 컴포넌트입니다. 왜냐하면 클릭과 상태 변화가 필요하기 때문입니다.
즉, 서버는 "좋아요 수의 시작값"을 넘겨주고, 클라이언트는 "클릭 후 변화"를 맡습니다.
이 분업이 Next.js의 핵심 감각입니다.
어떤 기준으로 나눠야 할까요?
실전에서는 늘 애매한 순간이 옵니다. 그럴 때는 이 질문 순서대로 생각하면 됩니다.
1단계: 사용자와 상호작용하나요?
버튼 클릭, 입력값 변경, 모달 열기, 탭 전환이 필요하면 클라이언트 컴포넌트일 가능성이 큽니다.
2단계: 브라우저 기능이 필요한가요?
window, document, localStorage, 위치 정보 같은 기능이 필요하면 클라이언트 컴포넌트가 필요합니다.
3단계: 서버에서 미리 준비하면 더 좋은가요?
데이터 읽기, 보안 정보 사용, 무거운 계산, 초기 내용 출력이라면 서버 컴포넌트가 잘 맞습니다.
4단계: 꼭 클라이언트로 보내야 하나요?
이 질문이 중요합니다. 상호작용이 없다면 굳이 클라이언트 컴포넌트로 만들 이유가 없습니다.
한 문장으로 압축하면 이렇게 정리할 수 있습니다.
움직여야 하면 클라이언트
준비해서 보여주면 되면 서버
비교표로 한 번에 정리하기
| 항목 | 서버 컴포넌트 | 클라이언트 컴포넌트 |
|---|---|---|
| 기본값 여부 | 기본값 | "use client" 필요 |
| 실행에 강한 장소 | 서버 | 브라우저 |
| 데이터 읽기 | 매우 적합 | 가능하지만 보통 비효율적 |
| 상태 관리 | 불가 | 가능 |
| 이벤트 처리 | 불가 | 가능 |
| 브라우저 API | 불가 | 가능 |
| 번들 크기 | 줄이기 좋음 | 늘어날 수 있음 |
| 대표 예시 | 페이지, 레이아웃, 목록, 상세 내용 | 버튼, 검색창, 모달, 탭 |
표를 볼 때 가장 중요한 줄은 이것입니다.
서버는 기본, 클라이언트는 예외적으로 추가
이 감각을 잊지 않으면 구조가 점점 단순해집니다.
서버 컴포넌트 안에 클라이언트 컴포넌트 넣기: 가능
이건 아주 흔한 패턴입니다.
import SearchBox from './search-box'
export default function Page() {
return (
<div>
<h1>기록 목록</h1>
<SearchBox />
</div>
)
}상위 페이지는 서버 컴포넌트이고, SearchBox는 클라이언트 컴포넌트일 수 있습니다. 이 구조는 자연스럽고 권장되는 방식입니다.
왜냐하면 페이지 전체는 서버에서 빠르게 준비하고, 검색창만 브라우저에서 살아 움직이면 되기 때문입니다.
클라이언트 컴포넌트에서 서버 컴포넌트 직접 가져오기: 피해야 합니다
반대로 클라이언트 컴포넌트 파일 안에서 서버 컴포넌트를 직접 가져오는 구조는 혼란을 만들기 쉽습니다.
초보자 기준으로는 이렇게 기억하면 충분합니다.
클라이언트 컴포넌트가 서버 컴포넌트를 직접 끌어오는 게 아니라, 상위 서버 컴포넌트가 둘을 조립한다고 생각하면 됩니다.
예를 들어 모달은 클라이언트 컴포넌트지만, 모달 안의 내용은 서버 컴포넌트가 될 수 있습니다. 이럴 때는 상위 서버 컴포넌트가 children으로 넣어 주는 방식이 깔끔합니다.
클라이언트 컴포넌트
'use client'
export default function Modal({ children }: { children: React.ReactNode }) {
return (
<div className="rounded-xl border bg-white p-4 shadow">
{children}
</div>
)
}상위 서버 컴포넌트
import Modal from './modal'
function CartSummary() {
return <div>오늘 담긴 기록 3개</div>
}
export default function Page() {
return (
<Modal>
<CartSummary />
</Modal>
)
}이 구조는 "클라이언트가 서버를 직접 호출한다"기보다, "서버가 완성된 조각을 클라이언트 껍데기 안에 넣어 준다"에 가깝습니다.
서버에서 클라이언트로 넘기는 props에는 규칙이 있습니다
서버 컴포넌트에서 클라이언트 컴포넌트로 데이터를 넘길 수 있습니다. 그런데 아무거나 넘길 수 있는 것은 아닙니다.
숫자, 문자열, 배열, 평범한 객체처럼 직렬화 가능한 값이어야 합니다.
쉽게 말해, 전송해서 다시 복원할 수 있는 데이터여야 합니다.
이건 괜찮습니다.
<FavoriteButton initialCount={3} />이건 문제를 만들 수 있습니다.
<FavoriteButton onLike={() => console.log('좋아요')} />함수는 서버에서 클라이언트로 그냥 props로 넘긴다고 생각하면 안 됩니다. 초보자 입장에서는 "데이터는 넘길 수 있지만, 브라우저에서 바로 실행할 함수는 함부로 못 넘긴다" 정도로 이해하면 충분합니다.
Context Provider는 보통 클라이언트 컴포넌트입니다
테마, 로그인 상태, 장바구니 상태처럼 앱 전반에서 공유하는 값을 다룰 때 React Context를 자주 씁니다. 그런데 Provider는 보통 클라이언트 컴포넌트로 두는 편이 안전합니다.
'use client'
import { createContext } from 'react'
export const ThemeContext = createContext('light')
export default function ThemeProvider({ children }: { children: React.ReactNode }) {
return (
<ThemeContext.Provider value="light">
{children}
</ThemeContext.Provider>
)
}그리고 서버 컴포넌트인 레이아웃에서 불러와 감쌀 수 있습니다.
import ThemeProvider from './theme-provider'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="ko">
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
)
}여기서 작은 팁이 하나 있습니다. Provider는 트리의 가장 위를 무조건 다 감싸기보다, 필요한 범위만 감싸는 편이 최적화에 유리합니다.
보안 관점에서도 서버 컴포넌트는 강력합니다
서버 컴포넌트의 매력은 성능만이 아닙니다. 보안에도 좋습니다.
예를 들어 외부 API를 호출할 때 비밀 키가 필요하다면, 그 코드를 서버 컴포넌트나 서버 전용 유틸 함수에 두는 편이 안전합니다. 브라우저에 민감한 정보를 보내지 않아도 되기 때문입니다.
'작은 기록 상점'에서도 나중에 관리자용 기능이나 비공개 데이터가 생긴다면, 그런 로직은 되도록 서버에 두는 습관이 좋습니다.
한마디로 말해, 숨겨야 하는 것은 서버에 두고, 반응해야 하는 것만 클라이언트로 보낸다입니다.
흔한 실수 1: 일단 안 되면 "use client"부터 붙이기
정말 자주 나오는 실수입니다.
에러가 나니까 무심코 파일 맨 위에 "use client"를 붙이는 것이죠. 물론 급한 불은 꺼질 수 있습니다. 하지만 이렇게 하면 서버에 남아 있어도 될 코드까지 브라우저 번들에 섞여 들어갈 수 있습니다.
예를 들어 목록 페이지 전체가 클라이언트 컴포넌트가 되어 버리면, 데이터 읽기와 정적 내용 출력까지 모두 클라이언트 쪽 부담이 커질 수 있습니다.
안 되는 기능이 있다면 먼저 질문해 보세요.
"정말 이 파일 전체가 상호작용해야 하나?"
대부분은 아닙니다. 보통은 버튼 하나, 입력창 하나만 따로 분리하면 해결됩니다.
흔한 실수 2: 서버 컴포넌트 안에서 useState나 onClick 쓰기
이건 개념이 익숙해지기 전까지 자주 부딪히는 부분입니다.
서버 컴포넌트에서는 상태와 이벤트를 직접 다룰 수 없습니다. 이런 기능이 필요하면 해당 부분만 클라이언트 컴포넌트로 쪼개세요.
예를 들어 이런 구조가 더 좋습니다.
export default function RecordCard({ title }: { title: string }) {
return (
<article>
<h2>{title}</h2>
<ToggleMemo />
</article>
)
}'use client'
import { useState } from 'react'
export default function ToggleMemo() {
const [open, setOpen] = useState(false)
return (
<button onClick={() => setOpen(!open)}>
{open ? '닫기' : '메모 열기'}
</button>
)
}카드는 서버에 두고, 토글 버튼만 클라이언트로 뽑아낸 셈입니다.
흔한 실수 3: 브라우저 전용 라이브러리를 서버 컴포넌트에서 바로 쓰기
캐러셀, 차트, 애니메이션 라이브러리처럼 브라우저 환경을 기대하는 도구들은 서버 컴포넌트에서 바로 쓰면 문제가 날 수 있습니다.
이럴 때는 작은 래퍼 컴포넌트를 만들어 클라이언트 컴포넌트로 감싸는 편이 안전합니다.
'use client'
import { SomeChart } from 'some-chart-library'
export default function ChartWrapper() {
return <SomeChart />
}그리고 서버 컴포넌트에서는 이 ChartWrapper를 가져다 쓰면 됩니다.
이 방식은 "브라우저 전용 기능은 브라우저 쪽으로 격리한다"는 점에서 아주 깔끔합니다.
흔한 실수 4: 서버 전용 코드를 클라이언트에서 실수로 가져오기
API 키가 들어 있는 함수, 데이터베이스 접근 코드, 파일 시스템 처리 코드를 클라이언트 컴포넌트에서 가져오면 큰일 날 것 같지만, 실제 프로젝트에서는 실수로 그런 일이 생기기도 합니다.
그래서 서버 전용 코드는 아예 서버 전용 파일로 분리하고, "이 코드는 절대 브라우저로 가면 안 된다"는 감각을 분명히 갖는 것이 중요합니다.
특히 환경 변수는 더 조심해야 합니다. 공개해도 되는 값과 공개하면 안 되는 값을 구분하는 습관이 꼭 필요합니다.
초보자를 위한 추천 전략: 일단 서버로 만들고, 필요한 조각만 클라이언트로
실무 감각까지 포함해서 가장 안전한 시작법은 이겁니다.
페이지와 레이아웃은 서버 컴포넌트로 시작합니다.
목록, 상세 내용, 정적인 카드도 우선 서버 컴포넌트로 둡니다.
클릭, 입력, 토글, 모달 같은 조각만 클라이언트 컴포넌트로 분리합니다.
"use client"는 정말 필요한 파일에만 붙입니다.
이 전략이 좋은 이유는 처음부터 성능과 구조를 동시에 챙길 수 있기 때문입니다. 브라우저에 부담을 덜 주고, 코드의 역할도 선명해집니다.
'작은 기록 상점'에 바로 적용해 보기
지금까지 만든 프로젝트를 기준으로 앞으로 이런 식으로 생각하면 좋습니다.
기록 목록 페이지는 서버 컴포넌트입니다. 기록 데이터를 읽고 카드 목록을 그립니다.
기록 상세 페이지도 서버 컴포넌트가 잘 어울립니다. 상세 설명, 날짜, 태그, 추천 기록 같은 내용은 서버에서 준비하기 좋습니다.
반면 사용자가 직접 만지는 부분은 클라이언트 컴포넌트 후보입니다.
검색어 입력창
좋아요 버튼
정렬 선택 드롭다운
"더 보기" 토글
장바구니 추가 버튼
이렇게 나누면 다음 장에서 데이터를 가져올 때도 자연스럽습니다. "데이터는 서버에서 가져오고, 상호작용은 클라이언트에서 처리한다"라는 큰 그림이 이미 잡혀 있기 때문입니다.
딱 두 문장으로 최종 정리
Next.js 최신 App Router에서는 서버 컴포넌트가 기본값입니다.
그리고 클라이언트 컴포넌트는 상호작용이 필요할 때만 꺼내 쓰는 도구입니다.
이 두 문장만 정말 제대로 이해해도, Next.js가 갑자기 훨씬 덜 무섭게 느껴집니다.
마무리
이번 장에서 우리는 "서버냐, 클라이언트냐"를 구분하는 기준을 배웠습니다. 중요한 건 멋진 용어가 아니라 역할입니다.
서버 컴포넌트는 준비하고, 클라이언트 컴포넌트는 반응합니다.
이제 다음 장에서는 이 감각을 바탕으로 실제 데이터를 가져와 보겠습니다. 목록과 상세 데이터를 어디서, 어떻게 가져오면 좋은지 보게 되면 이번 장의 내용이 더 또렷하게 연결될 것입니다. 그때부터는 Next.js가 그냥 신기한 프레임워크가 아니라, 이유를 알고 쓰는 도구로 바뀌기 시작합니다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.