← 블로그 목록
Next.js서버 컴포넌트성능 최적화렌더링SvelteKit

서버 컴포넌트 vs 클라이언트 컴포넌트: Next.js·SvelteKit 사이드 프로젝트에서 뭘 고를까

Next.js와 SvelteKit의 서버·클라이언트 컴포넌트 차이를 성능 관점에서 비교. 사이드 프로젝트에서 올바른 선택 기준과 AI 생성 코드의 흔한 함정을 다룬다.

Vibeollio 팀-

왜 지금 서버·클라이언트 컴포넌트 선택이 중요한가

사이드 프로젝트를 시작할 때 가장 먼저 마주치는 질문이 있다: "이 컴포넌트는 서버에서 렌더링할까, 클라이언트에서 할까?" Claude나 ChatGPT 같은 AI 도구로 코드를 생성받으면 더 헷갈린다. 생성된 코드가 기본값으로 클라이언트 컴포넌트를 쓰거나, 반대로 상태 관리가 필요한데 서버 컴포넌트로 만들어지는 경우가 많기 때문이다.

성능과 번들 크기, 그리고 개발 복잡도는 이 선택 하나에 달려 있다. Next.js 13 이상(App Router)이나 SvelteKit을 쓰는 프로젝트라면 더욱 그렇다.

서버 컴포넌트: 언제 쓸까

서버 컴포넌트는 서버에서만 실행되고, 그 렌더링 결과만 클라이언트로 전달된다. 번들 크기에 포함되지 않는다는 뜻이다.

데이터베이스 쿼리가 필요한 경우가 가장 명확하다. 예를 들어 게시글 목록을 보여주는 컴포넌트라면:

// app/components/PostList.jsx (Next.js 서버 컴포넌트)
export default async function PostList() {
  const posts = await fetch('https://api.example.com/posts').then(r => r.json());
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

API 호출이 서버에서 일어나므로 클라이언트 JavaScript 번들에 fetch 로직이 포함되지 않는다. 또한 API 엔드포인트를 숨길 수 있다.

정적 콘텐츠나 마크다운 렌더링, SEO가 중요한 메타데이터 처리 역시 서버 컴포넌트의 장점이다. 모든 HTML이 미리 준비되므로 검색 엔진이 바로 인덱싱할 수 있다.

클라이언트 컴포넌트: 상호작용이 필요하면

클라이언트 컴포넌트는 브라우저에서 실행되므로 상태(useState), 이벤트, 생명주기 훅을 쓸 수 있다.

폼 입력, 버튼 클릭, 실시간 업데이트가 필요한 곳이다. 예를 들어 검색창:

// app/components/SearchBox.jsx (Next.js 클라이언트 컴포넌트)
'use client';

import { useState } from 'react';

export default function SearchBox() {
  const [query, setQuery] = useState('');

  return (
    <input
      type="text"
      value={query}
      onChange={(e) => setQuery(e.target.value)}
      placeholder="검색..."
    />
  );
}

'use client' 지시문은 Next.js에 "이 컴포넌트와 그 자식들을 클라이언트 번들에 포함하라"고 말한다. 상태 변경이 즉시 반영되므로 사용자 경험이 부드럽다.

AI 생성 코드에서 흔한 실수 3가지

1. 모든 컴포넌트를 클라이언트로 만들기

AI는 안전하게 'use client'를 붙이는 경향이 있다. 클라이언트 컴포넌트는 번들 크기를 늘리고, 불필요한 JavaScript를 로드한다. 서버 컴포넌트로 충분한 정적 콘텐츠까지 클라이언트화하면 초기 로딩 속도가 떨어진다.

2. 서버 컴포넌트에서 상태 관리 시도하기

// 이건 작동하지 않는다
export default async function Counter() {
  const [count, setCount] = useState(0); // ❌ 서버 컴포넌트에서 불가
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

AI가 이런 코드를 생성하기도 한다. 서버 컴포넌트는 상태나 이벤트 핸들러를 지원하지 않는다.

3. 환경 변수를 클라이언트에 노출하기

클라이언트 컴포넌트에서 데이터베이스 연결 문자열이나 API 시크릿을 직접 쓰면 안 된다. 서버 컴포넌트나 API 라우트에서만 민감한 정보를 다뤄야 한다.

선택 기준: 의사결정 플로우

"이 컴포넌트가 상호작용(클릭, 입력, 토글)이 필요한가?"

  • 그렇다면 → 클라이언트 컴포넌트
  • 아니라면 → 서버 컴포넌트

"데이터베이스나 API에서 데이터를 가져와야 하는가?"

  • 그렇다면 → 서버 컴포넌트 (클라이언트 번들 크기 절감)
  • 아니라면 → 상황에 따라

"SEO나 초기 로딩 속도가 중요한가?"

  • 그렇다면 → 서버 컴포넌트 우선
  • 아니라면 → 유연하게 선택

실제로는 대부분의 페이지가 서버 컴포넌트 + 필요한 부분만 클라이언트 컴포넌트로 구성된다. 상호작용이 필요한 작은 부분(버튼, 폼)을 클라이언트 컴포넌트로 감싸고, 나머지는 서버에서 렌더링하는 방식이다.

성능 차이는 실제로 얼마나 날까?

번들 크기 관점에서 보면 차이가 크다. 서버 컴포넌트로 렌더링하는 부분이 많을수록 클라이언트 JavaScript는 줄어든다. 예를 들어 마크다운 파서 라이브러리나 데이터 처리 로직이 서버에만 있으면, 사용자의 브라우저는 그 라이브러리를 다운로드할 필요가 없다.

SvelteKit에는 React식 서버 컴포넌트가 없고, 대신 +page.server.js(서버에서만 실행되는 load 함수)와 +page.js(서버와 브라우저 양쪽에서 실행될 수 있는 universal load 함수)로 데이터 로딩 위치를 나눈다. DB 접근이나 비밀 키가 필요한 로직은 +page.server.js에 둔다.

실제 측정은 프로젝트마다 다르지만, 정적 콘텐츠가 많은 블로그나 문서 사이트라면 서버 컴포넌트 중심으로 구성했을 때 초기 로딩에 유리한 경우가 많다.

사이드 프로젝트에서 실전 팁

1단계: 페이지 레이아웃을 서버 컴포넌트로 시작

데이터 페칭, 메타데이터, 레이아웃 구조는 모두 서버에서 처리한다.

2단계: 상호작용이 필요한 부분만 클라이언트 컴포넌트로 분리

버튼, 폼, 토글, 드롭다운 같은 작은 단위로 쪼개서 'use client'를 붙인다.

3단계: AI 생성 코드는 의심하고 검증

생성된 코드에서 불필요한 'use client'를 찾아내고, 상태 관리가 정말 필요한지 확인한다.

FAQ

모든 컴포넌트를 서버 컴포넌트로 만들 수 없을까요?

버튼 클릭이나 폼 입력처럼 사용자 상호작용이 필요한 부분은 클라이언트 컴포넌트여야 한다. 서버 컴포넌트는 상태와 이벤트 핸들러를 지원하지 않기 때문에 onClick이나 onChange 같은 기능을 쓸 수 없다.

SvelteKit에서도 같은 방식으로 생각하면 되나요?

개념은 비슷하지만 구조가 다르다. SvelteKit에는 서버 컴포넌트가 없고, .svelte 컴포넌트는 서버에서 먼저 렌더링된 뒤 브라우저에서 이어받는다. 대신 +page.server.js는 서버에서만 실행되는 load 함수라 DB 접근이나 비밀 키 사용에 쓰고, +page.js는 서버와 브라우저 양쪽에서 실행될 수 있다. '서버에서 데이터를 가져오고 상호작용이 필요한 곳만 브라우저에서 처리한다'는 사고방식은 같다.

처음부터 최적화를 신경 써야 하나요, 아니면 나중에 리팩토링해도 되나요?

사이드 프로젝트라면 먼저 동작하는 코드를 만들고, 나중에 성능을 측정한 후 리팩토링하는 것도 좋은 전략이다. 다만 처음부터 서버/클라이언트 개념을 이해하고 있으면 불필요한 리팩토링을 줄일 수 있다.

결론: 선택의 기준은 "상호작용"과 "데이터"

서버 컴포넌트와 클라이언트 컴포넌트는 트레이드오프다. 서버 컴포넌트는 번들 크기와 성능에 유리하지만 상호작용을 지원하지 않는다. 클라이언트 컴포넌트는 유연하지만 JavaScript를 브라우저에 로드한다.

Next.js나 SvelteKit 프로젝트를 시작할 때는 기본을 서버 컴포넌트로 두고, 필요한 부분만 클라이언트화하는 방식을 추천한다. AI 생성 코드를 받았다면 불필요한 'use client'를 제거하고, 상태 관리가 정말 필요한지 한 번 더 검토하자.

자신의 프로젝트 구조를 다시 한번 점검해보고, 성능 개선 기회를 찾았다면 Vibeollio에 프로젝트를 등록해서 다른 개발자들의 피드백을 받아보자. 실제 사용자 관점의 의견은 최적화 결정에 큰 도움이 된다.