기술 포스트

HTML vs TSX: 에이전시 납품 시나리오 비교


Article

에이전시에서 UI 납품 방식이 바뀌면 실제로 어떤 차이가 생길까요?

말로만 설명하면 와닿지 않으니, 같은 컴포넌트를 두 가지 방식으로 만들어봤습니다. 하나는 기존 HTML/CSS 방식, 다른 하나는 TSX 컴포넌트 방식입니다.

시나리오 1: 블로그 포스트 카드

디자인 시안 기준:

  • 카드 테두리 + 패딩
  • 제목, 날짜, 설명 텍스트
  • 태그 뱃지 (여러 개)
  • 호버 시 테두리 색상 변경

기존 방식: HTML + CSS 분리 납품

<!-- post-card.html -->
<article class="post-card">
  <h2 class="post-card__title">Next.js로 포트폴리오 만들기</h2>
  <p class="post-card__date">2026-02-24</p>
  <p class="post-card__desc">
    웹퍼블리셔가 Next.js 환경에서 포트폴리오 사이트를 구축하는 전 과정
  </p>
  <ul class="post-card__tags">
    <li class="post-card__tag">Next.js</li>
    <li class="post-card__tag">웹퍼블리셔</li>
  </ul>
</article>
/* post-card.css */
.post-card {
  border: 1px solid #e7e5e4;
  background-color: #fff;
  padding: 20px;
  transition: border-color 0.2s;
}
.post-card:hover {
  border-color: #a8a29e;
}
.post-card__title {
  font-size: 1rem;
  font-weight: 700;
  color: #1c1917;
}
.post-card__date {
  font-size: 0.75rem;
  color: #78716c;
  margin-top: 4px;
}
.post-card__desc {
  font-size: 0.875rem;
  color: #57534e;
  margin-top: 8px;
  line-height: 1.6;
}
.post-card__tags {
  display: flex;
  gap: 6px;
  margin-top: 12px;
  list-style: none;
}
.post-card__tag {
  background-color: #f5f5f4;
  color: #78716c;
  font-size: 0.75rem;
  font-weight: 500;
  padding: 2px 8px;
}

개발자가 이걸 받으면 어떻게 될까요? HTML 보면서 JSX 문법으로 다시 쓰고, CSS를 Tailwind 클래스나 CSS Module로 변환하고, 데이터를 props로 받게 구조도 바꿔야 합니다. 이 작업만 30분~1시간이 걸립니다.

TSX 컴포넌트 방식

// PostCard.tsx
interface PostCardProps {
  title: string;
  date: string;
  description: string;
  tags: string[];
}

export default function PostCard({ title, date, description, tags }: PostCardProps) {
  return (
    <article className="border border-stone-200 bg-white p-5 transition-colors hover:border-stone-400">
      <h2 className="text-base font-bold text-stone-900">{title}</h2>
      <p className="mt-1 text-xs text-stone-400">{date}</p>
      <p className="mt-2 text-sm leading-relaxed text-stone-600">{description}</p>
      <ul className="mt-3 flex flex-wrap gap-1.5">
        {tags.map((tag) => (
          <li key={tag} className="bg-stone-100 px-2 py-0.5 text-xs font-medium text-stone-500">
            {tag}
          </li>
        ))}
      </ul>
    </article>
  );
}

개발자가 이걸 받으면? props 타입 확인하고 데이터 연결만 하면 됩니다. 마크업 재작성이 필요 없습니다. 5분이면 끝나요.


시나리오 2: 네비게이션

에이전시 프로젝트에서 가장 먼저 만드는 컴포넌트 중 하나입니다. 이것도 비교해볼게요.

HTML 방식

<nav class="gnb">
  <a href="/" class="gnb__logo">StudioX</a>
  <ul class="gnb__menu">
    <li><a href="/about" class="gnb__link gnb__link--active">About</a></li>
    <li><a href="/works" class="gnb__link">Works</a></li>
    <li><a href="/contact" class="gnb__link">Contact</a></li>
  </ul>
</nav>

문제가 보이시나요? gnb__link--active 클래스가 하드코딩 되어 있습니다. 어떤 페이지에서 어떤 메뉴가 활성화되는지, 개발자가 직접 로직을 짜야 합니다.

TSX 방식

// Navigation.tsx
"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";

const menus = [
  { label: "About", href: "/about" },
  { label: "Works", href: "/works" },
  { label: "Contact", href: "/contact" },
];

export default function Navigation() {
  const pathname = usePathname();

  return (
    <nav className="flex items-center justify-between px-6 py-4">
      <Link href="/" className="text-lg font-bold text-stone-900">
        StudioX
      </Link>
      <ul className="flex gap-6">
        {menus.map((menu) => (
          <li key={menu.href}>
            <Link
              href={menu.href}
              className={`text-sm transition-colors ${
                pathname === menu.href
                  ? "font-semibold text-stone-900"
                  : "text-stone-500 hover:text-stone-700"
              }`}
            >
              {menu.label}
            </Link>
          </li>
        ))}
      </ul>
    </nav>
  );
}

메뉴 데이터가 배열로 분리돼 있고, 현재 경로에 따라 활성화 스타일이 자동으로 적용됩니다. 개발자가 받아서 추가로 손댈 게 거의 없습니다.


차이 요약

항목 HTML 납품 TSX 납품
개발자 추가 작업 HTML → JSX 재작성 필요 데이터 연결만
파일 수 HTML + CSS 분리 TSX 단일 파일
재사용성 없음 (정적 HTML) props로 즉시 재사용
동적 처리 개발자가 로직 추가 컴포넌트 안에 포함
유지보수 마크업·스타일 별도 수정 컴포넌트 단위 관리

퍼블리셔가 TSX로 넘어가려면

HTML을 이미 잘 다루고 있다면, TSX는 생각보다 빨리 익힐 수 있습니다. 핵심 차이는 몇 가지 안 됩니다:

// 1. class → className
<div className="container">

// 2. 단독 태그는 자기닫힘
<img src="/logo.png" alt="로고" />
<br />

// 3. 동적 데이터는 {} 안에
<h1>{title}</h1>
<p>{description}</p>

// 4. 리스트는 map()
{items.map((item) => (
  <li key={item.id}>{item.name}</li>
))}

// 5. 조건부 렌더링
{isVisible && <div>보이는 내용</div>}
{status === "error" ? <ErrorMsg /> : <Content />}

이 다섯 가지 패턴만 손에 익으면 대부분의 UI 컴포넌트를 TSX로 만들 수 있습니다. 나머지는 실무를 하면서 자연스럽게 붙습니다.

솔직히 처음에는 className이 어색하고, map() 안에서 JSX를 리턴하는 게 이상하게 느껴질 수 있습니다. 근데 2주만 연습하면 오히려 HTML로 돌아가는 게 불편해집니다. 그 정도로 금방 적응되는 문법이에요.