# 챕터 10: 첫 번째 컴포넌트 만들기
## 서론
이전 챕터에서 컴포넌트의 개념을 배웠다면, 이제는 실제로 우리만의 컴포넌트를 처음부터 끝까지 만들어볼 시간입니다. 처음 컴포넌트를 만들 때는 어디서부터 시작해야 할지 막막할 수 있습니다. 하지만 걱정하지 마십시오. 이번 챕터에서는 가장 기본적인 버튼 컴포넌트부터 시작하여, 점차 복잡한 카드 컴포넌트까지 단계별로 만들어보겠습니다.
실습을 통해 컴포넌트를 만드는 과정을 체득하고, 자신만의 컴포넌트 라이브러리를 구축하는 첫걸음을 내디뎌보겠습니다. 이 챕터를 마치면 여러분은 어떤 디자인이든 컴포넌트로 만들 수 있는 자신감을 갖게 될 것입니다.
## 본론
### 버튼 컴포넌트 만들기 - 단계별 접근
가장 기본적이면서도 중요한 버튼 컴포넌트부터 만들어봅시다. 먼저 components 폴더가 없다면 생성하고, 그 안에 Button.jsx 파일을 만듭니다.
**Step 1: 가장 기본적인 버튼**
// components/Button.jsx
export default function Button() {
return (
<button>
클릭하세요
</button>
)
}
**Step 2: 스타일 추가하기**
// components/Button.jsx
export default function Button() {
return (
<button className="bg-blue-500 text-white px-4 py-2 rounded">
클릭하세요
</button>
)
}
**Step 3: 호버 효과와 트랜지션 추가**
// components/Button.jsx
export default function Button() {
return (
<button className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors duration-200">
클릭하세요
</button>
)
}
**Step 4: 클릭 이벤트 추가**
// components/Button.jsx
// 'use client'는 "이 컴포넌트는 브라우저에서 실행되어야 합니다"라고 Next.js에게 알려주는 지시어입니다.
// 클릭, 입력, 상태 변경 등의 상호작용이 필요한 컴포넌트에는 이 지시어가 필요합니다.
'use client'
export default function Button() {
const handleClick = () => {
alert('버튼이 클릭되었습니다!')
}
return (
<button
onClick={handleClick}
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded transition-colors duration-200 active:scale-95"
>
클릭하세요
</button>
)
}
이제 페이지에서 버튼을 사용해봅시다:
// app/page.js
import Button from '../components/Button'
export default function Home() {
return (
<div className="p-8">
<h1 className="text-2xl font-bold mb-4">나의 첫 버튼 컴포넌트</h1>
<Button />
</div>
)
}
### 카드 컴포넌트 만들기
이제 좀 더 복잡한 카드 컴포넌트를 만들어봅시다. 카드는 이미지, 제목, 설명, 버튼 등 여러 요소를 포함하는 컴포넌트입니다.
// components/Card.jsx
export default function Card() {
return (
<div className="max-w-sm rounded-lg overflow-hidden shadow-lg bg-white hover:shadow-xl transition-shadow duration-300">
{/* 이미지 영역 */}
<div className="h-48 bg-gradient-to-r from-purple-400 to-pink-400"></div>
{/* 콘텐츠 영역 */}
<div className="p-6">
{/* 카테고리 태그 */}
<span className="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mb-2">
#개발
</span>
{/* 제목 */}
<h2 className="font-bold text-xl mb-2">
카드 컴포넌트 제목
</h2>
{/* 설명 */}
<p className="text-gray-700 text-base mb-4">
이것은 카드 컴포넌트의 설명 부분입니다.
여러 줄의 텍스트를 포함할 수 있으며,
내용을 간략하게 요약하여 보여줍니다.
</p>
{/* 하단 정보 */}
<div className="flex items-center justify-between">
<div className="text-sm text-gray-600">
2024년 1월 15일
</div>
<button className="bg-purple-500 hover:bg-purple-600 text-white px-3 py-1 rounded text-sm transition-colors">
자세히 보기
</button>
</div>
</div>
</div>
)
}
### 프로필 카드 컴포넌트
사용자 프로필을 표시하는 특별한 카드 컴포넌트를 만들어봅시다:
// components/ProfileCard.jsx
export default function ProfileCard() {
return (
<div className="max-w-xs mx-auto bg-white rounded-xl shadow-md overflow-hidden hover:shadow-xl transition-shadow duration-300">
{/* 배경 이미지 */}
<div className="h-32 bg-gradient-to-r from-blue-500 to-purple-600"></div>
{/* 프로필 정보 */}
<div className="relative px-6 pb-6">
{/* 프로필 이미지 */}
<div className="absolute -top-12 left-1/2 transform -translate-x-1/2">
<div className="w-24 h-24 bg-white rounded-full border-4 border-white shadow-lg">
<div className="w-full h-full rounded-full bg-gradient-to-r from-green-400 to-blue-500 flex items-center justify-center text-white text-2xl font-bold">
JD
</div>
</div>
</div>
{/* 이름과 직책 */}
<div className="pt-14 text-center">
<h3 className="text-xl font-bold text-gray-800">홍길동</h3>
<p className="text-gray-600 text-sm">프론트엔드 개발자</p>
</div>
{/* 통계 */}
<div className="mt-4 flex justify-around border-t pt-4">
<div className="text-center">
<div className="text-2xl font-bold text-gray-800">42</div>
<div className="text-xs text-gray-600">프로젝트</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-gray-800">1.2k</div>
<div className="text-xs text-gray-600">팔로워</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-gray-800">128</div>
<div className="text-xs text-gray-600">팔로잉</div>
</div>
</div>
{/* 액션 버튼 */}
<div className="mt-4 flex gap-2">
<button className="flex-1 bg-blue-500 hover:bg-blue-600 text-white py-2 rounded-lg transition-colors">
팔로우
</button>
<button className="flex-1 border border-gray-300 hover:bg-gray-50 text-gray-700 py-2 rounded-lg transition-colors">
메시지
</button>
</div>
</div>
</div>
)
}
### 네비게이션 바 컴포넌트
웹사이트의 필수 요소인 내비게이션 바를 컴포넌트로 만들어봅시다:
// components/Navbar.jsx
export default function Navbar() {
return (
<nav className="bg-white shadow-lg">
<div className="max-w-7xl mx-auto px-4">
<div className="flex justify-between items-center h-16">
{/* 로고 */}
<div className="flex items-center">
<span className="text-2xl font-bold text-blue-600">
MyLogo
</span>
</div>
{/* 메뉴 - 데스크톱 */}
<div className="hidden md:flex items-center space-x-8">
<a href="#" className="text-gray-700 hover:text-blue-600 transition-colors">
홈
</a>
<a href="#" className="text-gray-700 hover:text-blue-600 transition-colors">
소개
</a>
<a href="#" className="text-gray-700 hover:text-blue-600 transition-colors">
서비스
</a>
<a href="#" className="text-gray-700 hover:text-blue-600 transition-colors">
포트폴리오
</a>
<a href="#" className="text-gray-700 hover:text-blue-600 transition-colors">
연락처
</a>
</div>
{/* CTA 버튼 */}
<div className="hidden md:flex items-center">
<button className="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg transition-colors">
시작하기
</button>
</div>
{/* 모바일 메뉴 버튼 */}
<div className="md:hidden flex items-center">
<button className="text-gray-700 hover:text-blue-600 focus:outline-none">
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
</div>
</div>
</nav>
)
}
### 모달 컴포넌트
사용자 상호작용을 위한 모달(팝업) 컴포넌트를 만들어봅시다:
// components/Modal.jsx
export default function Modal() {
return (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-8 max-w-md w-full mx-4 transform transition-all">
{/* 모달 헤더 */}
<div className="flex justify-between items-center mb-4">
<h2 className="text-2xl font-bold text-gray-800">
모달 제목
</h2>
<button className="text-gray-500 hover:text-gray-700">
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* 모달 본문 */}
<div className="mb-6">
<p className="text-gray-600">
이것은 모달 컴포넌트입니다. 중요한 정보를 표시하거나
사용자의 확인이 필요할 때 사용합니다.
</p>
</div>
{/* 모달 푸터 */}
<div className="flex justify-end gap-3">
<button className="px-4 py-2 text-gray-600 hover:text-gray-800 transition-colors">
취소
</button>
<button className="px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors">
확인
</button>
</div>
</div>
</div>
)
}
### 폼 입력 컴포넌트
사용자 입력을 받는 폼 컴포넌트를 만들어봅시다:
// components/ContactForm.jsx
export default function ContactForm() {
return (
<form className="max-w-lg mx-auto bg-white p-8 rounded-lg shadow-lg">
<h2 className="text-2xl font-bold mb-6 text-gray-800">
문의하기
</h2>
{/* 이름 입력 */}
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2">
이름
</label>
<input
type="text"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500 transition-colors"
placeholder="홍길동"
/>
</div>
{/* 이메일 입력 */}
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2">
이메일
</label>
<input
type="email"
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500 transition-colors"
placeholder="example@email.com"
/>
</div>
{/* 메시지 입력 */}
<div className="mb-6">
<label className="block text-gray-700 text-sm font-bold mb-2">
메시지
</label>
<textarea
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:border-blue-500 transition-colors h-32 resize-none"
placeholder="문의 내용을 입력해주세요..."
/>
</div>
{/* 제출 버튼 */}
<button
type="submit"
className="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-lg transition-colors"
>
전송하기
</button>
</form>
)
}
### 실습: 컴포넌트 조합하여 완전한 페이지 만들기
이제 우리가 만든 모든 컴포넌트를 조합하여 완전한 페이지를 구성해 봅시다:
// app/page.js
import Navbar from '../components/Navbar'
import Button from '../components/Button'
import Card from '../components/Card'
import ProfileCard from '../components/ProfileCard'
import ContactForm from '../components/ContactForm'
export default function ShowcasePage() {
return (
<div className="min-h-screen bg-gray-50">
<Navbar />
{/* 히어로 섹션 */}
<section className="py-20 bg-gradient-to-r from-blue-500 to-purple-600">
<div className="container mx-auto text-center text-white">
<h1 className="text-5xl font-bold mb-4">
컴포넌트 쇼케이스
</h1>
<p className="text-xl mb-8">
우리가 만든 모든 컴포넌트를 한 곳에서 확인하세요
</p>
<Button />
</div>
</section>
{/* 카드 섹션 */}
<section className="py-16 container mx-auto">
<h2 className="text-3xl font-bold text-center mb-12">
카드 컴포넌트
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 px-4">
<Card />
<Card />
<Card />
</div>
</section>
{/* 프로필 섹션 */}
<section className="py-16 bg-gray-100">
<div className="container mx-auto">
<h2 className="text-3xl font-bold text-center mb-12">
팀 멤버
</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 px-4">
<ProfileCard />
<ProfileCard />
<ProfileCard />
</div>
</div>
</section>
{/* 연락처 섹션 */}
<section className="py-16">
<div className="container mx-auto px-4">
<ContactForm />
</div>
</section>
</div>
)
}
### 컴포넌트 개발 팁
**1. 작게 시작하기**: 복잡한 컴포넌트도 작은 부분부터 시작합니다.
**2. 단계별 개선**: 기본 구조 → 스타일 → 상호작용 순으로 개발합니다.
**3. 재사용성 고려**: 다른 곳에서도 사용할 수 있도록 범용적으로 만듭니다.
**4. 명확한 책임**: 하나의 컴포넌트는 하나의 명확한 역할만 담당해야 합니다.
**5. 일관된 스타일**: 프로젝트 전체에서 일관된 디자인 패턴을 유지합니다.
## 결론
축하합니다! 여러분은 이제 다양한 종류의 컴포넌트를 직접 만들 수 있게 되었습니다. 버튼, 카드, 프로필 카드, 내비게이션 바, 모달, 폼 등 웹사이트의 핵심 구성 요소들을 모두 컴포넌트로 만들어보았습니다.
컴포넌트를 만드는 것은 처음에는 어려워 보일 수 있지만, 하나씩 만들다 보면 패턴이 보이기 시작합니다. 대부분의 컴포넌트는 비슷한 구조를 가지고 있으며, 스타일과 내용만 다를 뿐입니다.
이제 여러분은 어떤 디자인이든 컴포넌트로 만들 수 있는 기본기를 갖추었습니다. 다음 챕터에서는 Props를 사용하여 이 컴포넌트들을 더욱 유연하고 재사용 가능하게 만드는 방법을 배워보겠습니다. Props를 활용하면 같은 컴포넌트로도 다양한 모습을 표현할 수 있습니다!
'학습자료 > Next.js 초보자 학습 과정' 카테고리의 다른 글
| # 챕터 12: State - 변하는 데이터 관리하기 (3) | 2025.08.16 |
|---|---|
| # Next.js 가이드 / 챕터 11: Props - 컴포넌트에 데이터 전달하기 (3) | 2025.08.13 |
| # Next.js 가이드 / 챕터 9: Components - 레고 블록처럼 조립하기 (4) | 2025.08.11 |
| # Next.js 가이드 / 챕터 8: Tailwind CSS 실전 활용법 (7) | 2025.08.09 |
| # Next.js 가이드 / 챕터 7: Tailwind CSS 시작하기 (2) | 2025.08.09 |