웹사이트를 하나 만든다고 상상해 보세요.
•
만약 웹사이트 전체를 단 하나의 거대한 파일에 코드로 작성한다면 어떨까요?
•
아마 버튼 색상 하나 바꾸려고 해도 수천, 수만 줄의 코드를 헤매야 하는 끔찍한 상황이 벌어질 겁니다.
•
이때 등장하는 개념이 바로 Next.js 컴포넌트입니다.
1. Next.js 컴포넌트, 왜 레고 블록과 똑같을까요?
1.
독립성과 재사용성 (같은 블록 계속 쓰기)
레고에는 2x4 크기의 빨간색, 파란색 블록이 있습니다. 이 블록들은 성을 만들 때도, 자동차를 만들 때도 똑같이 사용할 수 있죠. 마찬가지로, 웹사이트에서 자주 보이는 '버튼', '검색창', '프로필 이미지' 같은 요소들을 한번 잘 만들어두면(컴포넌트화), 어떤 페이지에서든 가져다 쓸 수 있습니다. 코드를 복사-붙여넣기 할 필요가 없어 생산성이 폭발적으로 증가합니다.
2.
손쉬운 유지보수 (하나만 고치면 전부 해결)
레고 성의 창문 블록 디자인이 마음에 안 들면, 그 창문 블록만 쏙 빼서 다른 모양으로 바꾸면 됩니다. 성 전체를 부술 필요가 없죠. 마찬가지입니다. 웹사이트의 모든 버튼 디자인을 바꾸고 싶다면, Button 리액트 컴포넌트 파일 하나만 수정하면 됩니다. 그러면 그 버튼을 사용하던 수십 개의 페이지 디자인이 한 번에 통일성 있게 변경됩니다.
3.
조립 (작은 블록으로 큰 작품 만들기)
작은 레고 블록들을 합쳐 창문과 문을 만들고, 이것들을 다시 합쳐 하나의 '집'을 완성합니다. Next.js 컴포넌트도 똑같습니다. Avatar(프로필 사진), UserName(이름) 같은 작은 컴포넌트를 합쳐 Profile이라는 중간 크기 컴포넌트를 만들고, 이것들을 또 조립해 하나의 완성된 페이지를 만들어 나갑니다. 이렇게 코드를 '부품화'하면 관리가 훨씬 쉬워집니다.
2. Next.js의 핵심: 서버 컴포넌트 vs 클라이언트 컴포넌트
Next.js가 특별한 이유는 이 '컴포넌트' 개념을 서버와 클라이언트, 두 공간으로 확장했기 때문입니다. Next.js 13 버전부터는 모든 컴포넌트가 기본적으로 서버 컴포넌트로 동작합니다.
1.
서버 컴포넌트 (기본값, 성능 담당)서버 컴포넌트는 이름 그대로, 우리 눈에 보이지 않는 '서버' 컴퓨터에서 먼저 렌더링(화면을 그리는 작업)을 끝내는 똑똑한 녀석입니다.
•
특징: 서버에서 데이터베이스 정보를 가져오거나, 복잡한 계산을 미리 다 처리해서 최종 결과물인 HTML 코드만 사용자 브라우저에 보내줍니다.
•
장점: 사용자의 브라우저가 할 일이 줄어드니 초기 로딩 속도가 매우 빨라집니다. 또한, API 키나 비밀 정보들을 서버에만 안전하게 보관할 수 있어 보안에 유리합니다.
•
제약: 서버에서 모든 작업을 끝내고 오기 때문에, 사용자가 클릭하거나 입력하는 등의 '상호작용'은 처리할 수 없습니다. useState나 useEffect 같은 리액트 훅(Hook) 사용이 불가능합니다.
// app/page.tsx (기본적으로 서버 컴포넌트)
// 서버에서만 실행되는 함수로, 데이터를 미리 가져옵니다.
async function getPosts() {
const res = await fetch('<https://api.example.com/posts>');
return res.json();
}
// 이 페이지 컴포넌트는 서버에서 데이터를 모두 채워
// 완성된 HTML 형태로 브라우저에 전달됩니다.
export default async function HomePage() {
const posts = await getPosts();
return (
<main>
<h1>블로그 포스트</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</main>
);
}
JavaScript
복사
2.
클라이언트 컴포넌트 ('use client', 상호작용 담당)
사용자의 클릭에 반응하거나, 애니메이션 효과를 주거나, 입력창에 글자를 쓰는 등 '상호작용'이 필요한 부분은 클라이언트 컴포넌트로 만들어야 합니다. 만드는 방법은 간단합니다. 파일 맨 위에 'use client'; 라는 주문만 외워주면 됩니다.
•
특징: 전통적인 리액트 컴포넌트처럼 사용자의 브라우저에서 직접 실행됩니다.
•
장점: useState로 상태를 관리하고, onClick 같은 이벤트에 반응하는 등 동적인 UI를 자유롭게 만들 수 있습니다.
•
사용 시점: 버튼, 입력 폼, 캐러셀(이미지 슬라이드), 상태에 따라 내용이 바뀌는 메뉴 등 사용자와의 소통이 필요한 모든 곳에 사용합니다.
// components/Counter.tsx
// 파일 최상단에 'use client'를 선언해 클라이언트 컴포넌트로 만듭니다.
'use client';
import { useState } from 'react';
// 이 컴포넌트는 브라우저에서 직접 숫자를 세고 화면을 업데이트합니다.
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>현재 숫자: {count}</p>
<button onClick={() => setCount(count + 1)}>증가</button>
</div>
);
}
JavaScript
복사
3. 라우터와 컴포넌트는 어떤 관계일까요? (feat. 레스토랑)
"그럼 이 수많은 컴포넌트들을 라우터가 하나하나 다 불러오는 건가요?" 라는 궁금증이 생길 수 있습니다. 이 관계는 '레스토랑'에 비유하면 명확해집니다.
•
손님 & 메뉴판 (사용자 & URL): 손님(사용자)이 메뉴판을 보고 /profile 주소를 입력하며 "프로필 페이지 주세요"라고 주문합니다.
•
웨이터 (라우터): 주문을 받은 웨이터(Next.js 라우터)는 주방에 가서 /profile에 해당하는 요리법(페이지 컴포넌트)을 찾아달라고 요청합니다.
•
주방장 & 재료 (페이지 컴포넌트 & 일반 컴포넌트): 주방장(페이지 컴포넌트, 예: app/profile/page.tsx)은 '프로필 페이지'라는 요리를 만들기 위해 미리 준비해 둔 재료들(일반 컴포넌트, 예: Avatar, UserInfo, Button)을 가져와 멋지게 조립합니다.
•
서빙: 웨이터(라우터)는 주방장이 완성한 '프로필 페이지'라는 요리 전체를 손님에게 가져다줍니다. 재료인 아바타, 버튼을 각각 가져다주는 것이 아니라, 완성된 요리 한 접시를 통째로 서빙하는 것이죠.
즉, 라우터는 가장 큰 조립품인 '페이지 컴포넌트'를 불러오는 역할을 합니다. 그리고 그 페이지 컴포넌트 안에 우리가 미리 만들어 둔 수많은 작은 컴포넌트들이 포함되어 있는 구조입니다.
4. 컴포넌트 기반 개발, 왜 해야 할까요?
결론적으로, Next.js 컴포넌트를 기반으로 개발하는 것은 이제 선택이 아닌 필수입니다.
•
압도적인 생산성: 잘 만든 컴포넌트 하나로 반복 작업을 수십 번 줄일 수 있습니다.
•
쉬운 유지보수: 문제가 생기면 해당 부품(컴포넌트)만 교체하면 되니 유지보수가 극도로 편해집니다.
•
최고의 성능: 서버 컴포넌트를 적극적으로 활용해 웹사이트의 초기 로딩 속도를 극대화할 수 있습니다.
•
효율적인 협업: 여러 개발자가 각자 맡은 컴포넌트를 독립적으로 개발한 후 합칠 수 있어 팀 프로젝트에 매우 유리합니다.
Next.js에서 컴포넌트는 단순한 UI 조각을 넘어, 애플리케이션의 성능과 구조를 결정하는 핵심 설계 사상입니다. 서버 컴포넌트로 뼈대를 세워 성능을 확보하고, 상호작용이 필요한 곳에만 클라이언트 컴포넌트를 '섬'처럼 배치하는 방식을 이해하는 것이 현대적인 웹 개발의 첫걸음입니다.
오늘 배운 내용을 바탕으로 작은 프로젝트부터 컴포넌트 단위로 기능을 쪼개보는 연습을 시작해 보세요.
•
어떤 부분을 서버 컴포넌트로 남겨두고, 어떤 부분에 'use client'를 붙여야 할지 고민하는 과정 자체가 여러분의 Next.js 실력을 한 단계 끌어올릴 것입니다.