본문 바로가기
C.W.K.
Stream
Lesson 04 of 08 · published

서버와 클라이언트를 고르는 기준

~18 min · server vs client, decision, tree

Level 0호기심
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

기본은 서버, 필수 조건이 있을 때만 승격해

컴포넌트를 만들 때 “어디서 돌릴까?”를 취향으로 고르지 마. 브라우저에서만 가능한 요구가 있는지 차례로 물으면 답이 나와.

이 기능이 필요한가?
├── useState / useEffect / useReducer  → 클라이언트
├── onClick / onChange / onSubmit      → 클라이언트
├── window / navigator 같은 API       → 클라이언트
├── hook을 요구하는 라이브러리         → 클라이언트
└── 모두 아니라면                     → 서버

대부분이 서버인 트리가 정상적인 모양이야

전형적인 Next.js 앱에서는 컴포넌트의 80~90%가 서버 컴포넌트일 수 있어. 페이지, 목록, 카드, 헤더, 푸터, 글, 표는 데이터를 받아 그리는 일만 하니 서버에 남기기 좋아. 상태나 이벤트를 가진 작은 잎만 'use client' 경계를 가져.

이름보다 요구를 봐

  • 글 본문과 Markdown 변환기는 서버에 둬.
  • 제어 입력을 가진 검색창과 localStorage를 쓰는 테마 토글은 클라이언트야.
  • 좋아요 버튼은 낙관적 UI를 가진 작은 클라이언트 껍데기로 만들고, 초기 데이터와 변경 로직은 서버에서 받아.

“버튼처럼 보인다”가 아니라 실제로 브라우저 상태가 필요한지가 경계를 결정해.

한 페이지에서 use client 선언 수를 세는 것보다 각 선언 아래 import 그래프의 크기를 봐. 작은 잎 여러 개는 하나의 큰 클라이언트 페이지보다 쌀 수 있어. 상태가 여러 잎에서 공유된다면 provider 범위를 필요한 하위 트리로 제한하고 서버 children을 슬롯으로 넘겨.

80~90%라는 비율은 목표 점수가 아니라 전형적인 결과야. 편집기나 캔버스처럼 상호작용이 제품의 대부분인 화면은 클라이언트 비중이 높아도 맞아. 억지로 서버 비율을 높이느라 상태를 왕복시키기보다 사용자 행동과 데이터 보안에 맞는 경계를 골라. 페이지 트리의 각 노드에 상태·이벤트·브라우저 API·서버 데이터 표시를 표시하고 예상 경계를 그려. 실제 use client 위치와 비교해 너무 넓은 경계를 한 단계씩 낮춘 뒤 초기 HTML과 상호작용, bundle 크기가 모두 유지되는지 시험해.

검색창의 입력 상태는 클라이언트가 맡되 결과 목록과 권한이 필요한 데이터는 서버가 맡아. 두 경계 사이에 넘기는 값이 직렬화 가능한 최소 입력인지 확인해.

Code

서버에서 블로그 페이지 렌더링하기·tsx
// app/blog/[slug]/page.tsx — 서버
export default async function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const post = await fetchPost(slug);
  return <article>{post.content}</article>;
}
클라이언트 검색창에서 상태 읽고 쓰기·tsx
// components/SearchBar.tsx — 클라이언트
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';

export function SearchBar() {
  const [q, setQ] = useState('');
  const router = useRouter();
  return (
    <form onSubmit={(e) => { e.preventDefault(); router.push(`/search?q=${encodeURIComponent(q)}`); }}>
      <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Search" />
    </form>
  );
}

External links

Exercise

컴포넌트 열 개를 골라 서버와 클라이언트 중 어디에 둘지 판단해. 이유가 없는 'use client'를 찾아 가장 작은 경계로 내려.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.