취미중독

하고 싶은 일이 너무 많아

학습자료/Next.js 초보자 학습 과정

# Next.js 가이드 / 챕터 10: 첫 번째 컴포넌트 만들기

depilled 2025. 8. 12. 19:44

# 챕터 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를 활용하면 같은 컴포넌트로도 다양한 모습을 표현할 수 있습니다!