Skip to main content
Summary

같은 화면을 여러 번 복사하면 생기는 문제

지난 장에서 about, services, contact 같은 페이지를 만들었습니다. 페이지가 늘어나면 자연스럽게 이런 일이 생깁니다.

상단 메뉴는 모든 페이지에 필요합니다. 하단 회사 정보도 모든 페이지에 필요합니다. 서비스 소개 카드는 여러 개 반복됩니다. 문의 버튼도 여기저기 들어갑니다.

처음에는 복사해서 붙여 넣으면 빠릅니다. 하지만 나중에 문제가 생깁니다.

예를 들어 상단 메뉴에 블로그를 추가해야 한다고 해 봅시다. 메뉴 코드를 모든 페이지에 복사해 두었다면 app/page.tsx, app/about/page.tsx, app/services/page.tsx, app/contact/page.tsx를 모두 열어 고쳐야 합니다. 하나라도 빼먹으면 어떤 페이지에는 블로그 메뉴가 있고, 어떤 페이지에는 없습니다.

컴포넌트는 이 문제를 줄이기 위한 방법입니다. 반복되는 화면 조각을 한 파일로 빼 두고, 필요한 곳에서 다시 사용하는 방식입니다.

반복되는 화면은 컴포넌트로 정리하기AI 생성 이미지

컴포넌트는 화면을 만드는 레고 블록입니다

컴포넌트는 화면 조각입니다. 레고 블록처럼 생각하면 쉽습니다.

레고로 집을 만들 때 창문 블록, 문 블록, 지붕 블록을 따로 만들어 조립합니다. 웹사이트도 비슷합니다. 헤더, 푸터, 카드, 버튼 같은 조각을 따로 만들고 페이지에서 조립합니다.

대표적인 컴포넌트는 다음과 같습니다.

컴포넌트역할
Header상단 로고와 메뉴
Footer하단 회사 정보, 저작권, 연락처
Card서비스, 글, 상품 등을 담는 작은 박스
Button문의하기, 더 보기 같은 클릭 영역

컴포넌트는 보통 대문자로 시작합니다. Header, Footer, ServiceCard처럼 이름만 봐도 어떤 화면 조각인지 알 수 있게 짓습니다.

Next.js에서 페이지 파일은 app 폴더 안의 page.tsx가 담당합니다. 반면 컴포넌트 파일은 주소를 만드는 파일이 아닙니다. 화면 조각을 보관하는 파일입니다. 그래서 보통 app 폴더와 나란히, 프로젝트 루트에 components 폴더를 만들어 넣습니다.

my-next-site
├─ app
│  ├─ page.tsx
│  ├─ about
│  │  └─ page.tsx
│  └─ layout.tsx
├─ components
│  ├─ Header.tsx
│  ├─ Footer.tsx
│  └─ Card.tsx
└─ public

components 폴더는 꼭 이 이름이어야만 하는 것은 아닙니다. 하지만 많은 프로젝트에서 이 이름을 사용합니다. 개발자와 협업할 때도 이해하기 쉽습니다.

Header 컴포넌트 만들기

먼저 상단 메뉴를 컴포넌트로 분리해 보겠습니다.

프로젝트 루트에 components 폴더를 만들고, 그 안에 Header.tsx 파일을 만듭니다.

import Link from 'next/link';

export default function Header() {
  return (
    <header>
      <strong>My Next Site</strong>
      <nav>
        <Link href='/'>홈</Link>
        <Link href='/about'>소개</Link>
        <Link href='/services'>서비스</Link>
        <Link href='/contact'>문의</Link>
      </nav>
    </header>
  );
}

처음 보면 낯선 단어가 몇 개 있습니다.

function은 작은 기계라고 생각하면 됩니다. 버튼을 누르면 정해진 일을 하는 기계처럼, Header라는 함수는 상단 메뉴 화면을 만들어 냅니다.

export default는 이 파일 밖에서도 이 컴포넌트를 사용할 수 있게 내보내는 표시입니다. 물건에 이름표를 붙여 창고 밖으로 꺼낼 수 있게 하는 것과 비슷합니다.

import는 다른 파일에서 필요한 것을 가져오는 표시입니다. 여기서는 Next.js가 제공하는 Link를 가져왔습니다. Link는 사이트 안의 다른 페이지로 이동할 때 사용하는 연결 도구입니다.

이제 Header를 여러 페이지에 직접 넣을 수도 있습니다. 하지만 상단 메뉴는 모든 페이지에 공통으로 보이는 것이 자연스럽습니다. 그래서 app/layout.tsx에 넣는 것이 좋습니다.

app/layout.tsx를 열고, 위쪽에 Header를 가져옵니다.

import Header from '@/components/Header';

이 책에서 만든 프로젝트처럼 @ 경로 별칭이 설정되어 있다면 위 코드처럼 쓸 수 있습니다. 만약 여기서 빨간 줄이 보이거나 Module not found 오류가 난다면, 뒤쪽의 오류 확인 순서에서 import 경로를 다시 점검하면 됩니다.

그리고 <body> 안에서 {children} 위에 <Header />를 넣습니다.

<body>
  <Header />
  {children}
</body>

여기서 {children}은 각 페이지 내용이 들어오는 자리입니다. 액자에 비유하면 layout.tsx는 액자이고, {children}은 그 안에 갈아 끼우는 사진입니다. Header를 {children} 위에 두면 모든 사진 위에 같은 상단 메뉴가 붙습니다.

저장한 뒤 브라우저에서 /, /about, /services, /contact를 차례로 열어 보세요. 모든 페이지 위에 같은 상단 메뉴가 보이면 성공입니다.

앞으로 메뉴 문구나 링크를 바꾸고 싶다면 여러 페이지를 열 필요가 없습니다. components/Header.tsx만 고치면 됩니다.

Footer도 같은 방식으로 정리하기

하단 정보도 반복되기 쉽습니다. components/Footer.tsx 파일을 만듭니다.

export default function Footer() {
  return (
    <footer>
      <p>© 2026 My Next Site</p>
      <p>문의: [email protected]</p>
    </footer>
  );
}

그리고 app/layout.tsx에서 가져옵니다.

import Footer from '@/components/Footer';

<body> 안에서는 {children} 아래에 넣습니다.

<body>
  <Header />
  {children}
  <Footer />
</body>

실제 layout.tsx에는 이미 다른 코드가 들어 있을 수 있습니다. 그런 코드를 모두 지우라는 뜻은 아닙니다. 핵심은 <Header />는 {children} 위쪽에, <Footer />는 {children} 아래쪽에 둔다는 점입니다.

이제 모든 페이지는 같은 구조를 갖습니다.

위에는 Header, 가운데는 각 페이지 내용, 아래에는 Footer입니다. 페이지마다 같은 코드를 복사하지 않아도 됩니다.

이것만으로도 관리가 훨씬 쉬워집니다. 회사 전화번호가 바뀌면 Footer.tsx 하나만 고치면 됩니다. 메뉴가 바뀌면 Header.tsx 하나만 고치면 됩니다.

Card 컴포넌트로 반복되는 박스 줄이기

이번에는 서비스 소개 카드를 만들어 보겠습니다.

서비스 페이지에 이런 내용이 있다고 해 봅시다.

<section>
  <article>
    <h3>홈페이지 제작</h3>
    <p>작은 브랜드와 개인 사업자를 위한 소개 사이트를 만듭니다.</p>
  </article>

  <article>
    <h3>랜딩페이지 제작</h3>
    <p>광고나 이벤트에 맞춘 한 페이지 홍보 화면을 만듭니다.</p>
  </article>

  <article>
    <h3>콘텐츠 사이트 정리</h3>
    <p>글, 이미지, 자료를 보기 좋게 정리한 사이트를 만듭니다.</p>
  </article>
</section>

구조가 거의 같습니다. 제목만 다르고 설명만 다릅니다. 이럴 때 Card 컴포넌트를 만들 수 있습니다.

components/Card.tsx 파일을 만듭니다.

type CardProps = {
  title: string;
  description: string;
};

export default function Card({ title, description }: CardProps) {
  return (
    <article>
      <h3>{title}</h3>
      <p>{description}</p>
    </article>
  );
}

여기서 props라는 개념이 나옵니다. props는 컴포넌트에 건네주는 값입니다. 주문서라고 생각하면 쉽습니다.

카페에서 같은 컵을 쓰더라도 주문서에 따라 아메리카노가 될 수도 있고 라테가 될 수도 있습니다. Card도 같은 모양을 쓰지만, 전달하는 title과 description에 따라 다른 카드가 됩니다.

string은 글자 데이터라는 뜻입니다. 여기서는 제목과 설명이 글자로 들어온다고 표시한 것입니다. 처음부터 완벽히 외울 필요는 없습니다. 일단 title은 카드 제목, description은 카드 설명이라고 이해하면 충분합니다.

이제 페이지에서 카드를 사용할 수 있습니다.

import Card from '@/components/Card';

export default function ServicesPage() {
  return (
    <main>
      <h1>서비스</h1>
      <section>
        <Card
          title='홈페이지 제작'
          description='작은 브랜드와 개인 사업자를 위한 소개 사이트를 만듭니다.'
        />
        <Card
          title='랜딩페이지 제작'
          description='광고나 이벤트에 맞춘 한 페이지 홍보 화면을 만듭니다.'
        />
        <Card
          title='콘텐츠 사이트 정리'
          description='글, 이미지, 자료를 보기 좋게 정리한 사이트를 만듭니다.'
        />
      </section>
    </main>
  );
}

화면에서 보이는 내용은 이전과 거의 같습니다. 하지만 관리 방식이 달라졌습니다.

카드 안에서 제목을 <h3>로 보여 줄지, 굵은 글씨로 보여 줄지, 설명을 어디에 둘지는 Card.tsx에서 관리합니다. 각 페이지에서는 카드에 들어갈 내용만 전달합니다.

다음 장에서 디자인을 바꿀 때도 이 구조가 도움이 됩니다. 카드 테두리, 여백, 배경색을 한 번에 바꾸고 싶다면 Card.tsx를 고치면 됩니다.

버튼 문구도 props로 바꿀 수 있습니다

버튼도 좋은 컴포넌트 예시입니다.

components/Button.tsx 파일을 만듭니다.

import Link from 'next/link';

type ButtonProps = {
  label: string;
  href: string;
};

export default function Button({ label, href }: ButtonProps) {
  return <Link href={href}>{label}</Link>;
}

이 버튼은 두 가지 값을 받습니다.

label은 화면에 보이는 문구입니다. href는 클릭했을 때 이동할 주소입니다.

페이지에서는 이렇게 사용할 수 있습니다.

import Button from '@/components/Button';

export default function Home() {
  return (
    <main>
      <h1>작은 브랜드를 위한 웹사이트</h1>
      <p>Next.js로 빠르고 관리하기 쉬운 사이트를 만듭니다.</p>
      <Button label='문의하러 가기' href='/contact' />
    </main>
  );
}

문구를 바꾸고 싶다면 label만 바꾸면 됩니다.

<Button label='무료 상담 신청하기' href='/contact' />

이처럼 props는 컴포넌트를 매번 새로 만들지 않고도 내용을 바꾸는 방법입니다. 같은 버튼 모양을 유지하면서 문구와 이동 주소만 바꿀 수 있습니다.

작고 이름이 분명한 컴포넌트가 좋습니다

컴포넌트를 배운 뒤 흔히 하는 실수는 너무 큰 컴포넌트를 만드는 것입니다.

예를 들어 HomePageEverything이라는 컴포넌트 하나에 상단 메뉴, 소개 문구, 서비스 카드, 후기, 문의 버튼, 하단 정보까지 모두 넣으면 어떨까요? 처음에는 파일이 하나라 쉬워 보입니다. 하지만 나중에는 어디를 고쳐야 할지 찾기 어려워집니다.

좋은 컴포넌트는 역할이 작고 이름이 분명합니다.

좋은 예시는 다음과 같습니다.

  • Header: 상단 메뉴

  • Footer: 하단 정보

  • ServiceCard: 서비스 소개 카드

  • PostCard: 글 목록 카드

  • PrimaryButton: 중요한 행동 버튼

  • ContactInfo: 연락처 정보

애매한 이름은 피하는 것이 좋습니다.

  • Box

  • Thing

  • Content

  • MainComponent

  • NewComponent

물론 항상 완벽한 이름을 지을 필요는 없습니다. 기준은 간단합니다. 파일 이름만 보고도 어떤 화면 조각인지 떠올릴 수 있으면 좋은 이름입니다.

또 하나의 기준은 반복 여부입니다. 같은 화면 구조가 두 번 이상 나오면 컴포넌트로 뺄 만합니다. 반복되지 않더라도 페이지가 너무 길어져 읽기 어렵다면, 의미 단위로 나누는 것도 좋습니다.

비개발자가 요청하기 좋은 컴포넌트 정리법

비개발자가 개발자와 협업할 때 컴포넌트 단위로 말하면 소통이 쉬워집니다.

막연히 이렇게 말하는 것보다,

  • 메인 화면 좀 예쁘게 정리해 주세요.

다음처럼 말하는 편이 좋습니다.

  • 상단 메뉴를 Header 컴포넌트로 분리해 주세요.

  • 서비스 목록을 ServiceCard 컴포넌트로 만들어 주세요.

  • 카드마다 제목, 설명, 링크만 바꿀 수 있게 해 주세요.

  • 문의 버튼은 여러 페이지에서 재사용할 수 있게 Button 컴포넌트로 만들어 주세요.

요청할 때는 아래 네 가지를 정리하면 충분합니다.

정리할 내용예시
컴포넌트 이름ServiceCard
어디에 쓰는지서비스 페이지, 홈 화면 일부
바뀌는 내용제목, 설명, 링크
고정되는 내용카드 구조, 버튼 위치, 기본 디자인

예를 들어 개발자에게 이렇게 전달할 수 있습니다.

ServiceCard 컴포넌트를 만들어 주세요.
각 카드마다 title, description, href를 바꿀 수 있으면 좋겠습니다.
홈 화면과 서비스 페이지에서 같이 쓰고 싶습니다.

이 정도만 정리해도 개발자는 구조를 잡기 훨씬 쉽습니다. 직접 코드를 모두 작성하지 않더라도, 어떤 단위로 나누어야 하는지 이해하면 협업 품질이 좋아집니다.

자주 만나는 오류와 확인 순서

컴포넌트를 만들 때 자주 만나는 오류는 대부분 파일 이름, 위치, 가져오기 문제입니다.

Module not found라는 메시지가 보이면 import 경로를 확인하세요.

import Header from '@/components/Header';

components/Header.tsx 파일이 실제로 있는지 확인합니다. Header.tsx와 header.tsx는 다르게 취급될 수 있습니다. 대소문자를 맞추는 습관을 들이세요.

만약 @/components/Header에서 계속 오류가 난다면 프로젝트에 @ 경로 별칭이 없을 수도 있습니다. 이때는 현재 파일 위치를 기준으로 ../components/Header처럼 상대 경로를 쓰거나, 프로젝트 설정을 확인해야 합니다. 비개발자라면 이 부분은 개발자에게 "import 경로 별칭을 확인해 달라"고 요청해도 좋습니다.

Header is not defined라는 메시지가 보이면 파일에서 Header를 사용했지만 import하지 않았을 가능성이 큽니다. 사용하는 파일 위쪽에 import 문이 있는지 확인합니다.

Property 'title' is missing 같은 메시지가 보이면 필요한 props를 빠뜨렸다는 뜻입니다.

<Card description='설명만 있는 카드' />

위 코드는 title이 빠져 있습니다. Card.tsx에서 title을 필요하다고 정했기 때문에 페이지에서도 전달해야 합니다.

<Card title='홈페이지 제작' description='설명도 함께 넣습니다.' />

Expected corresponding JSX closing tag라는 메시지가 보이면 태그를 닫지 않았을 수 있습니다. <section>을 열었으면 </section>으로 닫아야 합니다. <Card />처럼 혼자 끝나는 컴포넌트는 끝에 /를 넣습니다.

오류가 났을 때는 다음 순서로 확인하면 됩니다.

  1. 파일 이름이 맞는가?

  2. 파일 위치가 맞는가?

  3. import 경로가 맞는가?

  4. 컴포넌트 이름의 대소문자가 맞는가?

  5. 필요한 props를 모두 전달했는가?

  6. 태그를 제대로 닫았는가?

처음에는 오류가 낯설지만, 대부분은 이 범위 안에서 해결됩니다.

직접 할 수 있는 일과 맡기면 좋은 일

비개발자도 직접 할 수 있는 일이 있습니다.

  • Header의 메뉴 문구 바꾸기

  • Footer의 이메일, 전화번호 바꾸기

  • Card에 들어가는 제목과 설명 바꾸기

  • Button의 문구와 이동 주소 바꾸기

  • 반복되는 화면을 찾아 컴포넌트 후보로 정리하기

반면 다음 작업은 전문가에게 맡기는 편이 안전합니다.

  • 클릭하면 창이 열리고 닫히는 복잡한 메뉴

  • 로그인 상태에 따라 달라지는 헤더

  • 결제, 예약, 회원 정보와 연결되는 버튼

  • 외부 데이터베이스에서 카드 목록을 자동으로 불러오는 구조

  • 사이트 전체 디자인 시스템 설계

컴포넌트는 단순히 코드를 줄이는 기술이 아닙니다. 사이트를 관리하기 쉽게 나누는 방법입니다. 반복되는 부분은 한 곳에서 관리하고, 바뀌는 내용은 props로 전달합니다.

이 원리를 이해하면 Next.js 프로젝트가 훨씬 덜 복잡해 보입니다. 페이지는 큰 화면이고, 컴포넌트는 그 화면을 이루는 작은 조각입니다. 앞으로 사이트가 커질수록 이 조각 나누기가 큰 힘을 발휘합니다.

책 목록

비개발자를 위한 Next.js 입문

전체 목차 보기

이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.

틸노트는 사용자가 AI를 활용해 노트를 쉽게 작성하거나 편집할 수 있는 서비스입니다.

AI 스퀘어 단톡방에 함께해요

AI 활용과 틸노트 사용 팁을 나누는 오픈채팅방입니다. 입장 비밀번호는 tilnote입니다.

참여하기