개요
React 개발을 시작한 지 얼마 안 됐을 때, 나의 컴포넌트는 대충 이런 모습이었다. ▼
<div>
<div>
<div>
...
<div>
<div>
</div>
솔직하게 말하면 지금도 종종 저러고 있는거 같다. 끝없이 중첩된 `div`들... 당시에는 어떤 문제가 생기는지도 모르고 굉장히 편리하네라고 생각했지만, 몇 개월 후 그 코드를 다시 열어봤을 때 이게 뭐하는 `div`였더라?라는 질문만 남았다. 여러 프로젝트를 경험하며 왜 모든 걸 `div`로 해결하면 안 되는가?라는 질문에 대한 답을 찾아가게 되었다.
그래서 이번 글에서는 단순히 시맨틱 HTML을 써라는 교과서적인 이야기가 아니라, 실제로 `div`를 남발했을 때 어떤 고통이 기다리고 있는지를 내 경험과 함께 풀어보려고 한다.
그전에... 왜 div만 쓰게 될까?
솔직히 `div`는 정말 편하다. 아무 기본 스타일도 없고, 블록 요소라서 레이아웃 잡기도 쉽고, 무엇보다 생각할 게 없다. ▼
// 이렇게 시작했다가...
<div className="container">
<div className="header">
<div className="title">My App</div>
</div>
<div className="content">
<div className="sidebar">
<div className="menu-item">Home</div>
<div className="menu-item">About</div>
</div>
<div className="main">
<div className="card">...</div>
</div>
</div>
</div>
위의 코드처럼 하면 그나마 깔끔해 보이긴 하지만, tailwind가 들어가게 되면 가독성이 쉽지 않아진다.
문제
문제 1: 대체 뭐가 뭔지 모르겠다
`div` 수프의 탄생
프로젝트가 조금만 커져도 이런 괴물이 탄생한다. ▼
<div>
<div>
<div>
<div>
<div>
<div>실제 콘텐츠</div>
</div>
</div>
</div>
</div>
</div>
실제로 이런 코드를 본 적이 있다. 각 `div`가 뭘 하는지 알아내려면 CSS 파일을 열어서 클래스명을 하나하나 확인해야 했다.
DevTools에서의 악몽
크롬 개발자 도구를 열면 더 알 수 없다. ▼
<div>
<div>
<div>
<div> <!-- 이게 뭐하는 div? -->
<div> <!-- 이건 또 뭐? -->
<div> <!-- 아 몰라... -->
엘리먼트 탭이 div로만 구성이 되어 원하는 요소를 찾으려면 하나하나 열어보거나 클래스명에 의존해야 한다. 클래스명이라도 제대로 있으면 다행인데, tailwind를 사용하는 경우 클래스명으로도 파악하기가 어려워진다.
문제 2: 접근성이 바닥을 뚫는다
스크린 리더가 읽지 못한다
시각 장애인 사용자가 우리 사이트를 방문한다고 상상해보자. 스크린 리더가 읽는 내용은 아래와 같아진다. ▼
"그룹, 그룹, 그룹, 텍스트: 홈, 그룹, 그룹, 텍스트: 소개..."
그룹만 보고서는 뭐가 뭔지를 도저히 알 길이 없다. 일반인의 시점에서는 버튼으로 보이거나, 컨테이너로 보이지만 시각 장애인 입장에서는 그저 그룹으로만 인식 될 뿐이다.
반면 시맨틱 HTML을 쓴다면, 훨씬 더 명확해진다. ▼
"네비게이션, 링크: 홈, 링크: 소개, 메인 콘텐츠 영역..."
키보드 네비게이션의 미로
또한 div로만 만든 페이지는 키보드 사용자에게 미로와 같다. ▼
// ❌ Tab 키로 접근 불가능한 네비게이션
<div className="nav">
<div onClick={() => navigate('/home')}>Home</div>
<div onClick={() => navigate('/about')}>About</div>
</div>
// ✅ 자연스러운 키보드 네비게이션
<nav>
<a href="/home">Home</a>
<a href="/about">About</a>
</nav>
문제 3: SEO가 죽는다
구글봇도 우리 사이트를 이해하지 못하게 된다. 검색 엔진은 HTML 구조를 보고 페이지의 의미를 파악한다. div 수프를 보면 검색 엔진도 혼란스럽다. ▼
<!-- ❌ 검색 엔진: "뭔 소리야 이게?" -->
<div class="big-text">우리 회사 소개</div>
<div class="small-text">
<div>우리는 최고의 서비스를...</div>
</div>
<!-- ✅ 검색 엔진: "아, 회사 소개 페이지구나!" -->
<h1>우리 회사 소개</h1>
<section>
<p>우리는 최고의 서비스를...</p>
</section>
시맨틱 마크업을 제대로만 해도 검색 순위가 올라갈 여지가 충분해진다. 물론 그렇다고 검색 순위가 무조건 올라간다는 것은 아니지만, 기본 중의 기본이다.
문제 4: 성능도 나빠진다
불필요한 DOM 깊이
div를 중첩하면 DOM 트리가 깊어진다. 브라우저는 이를 렌더링하고 리플로우할 때 더 많은 계산을 해야 한다. ▼
// ❌ 불필요한 중첩
<div className="card-wrapper">
<div className="card-container">
<div className="card-inner">
<div className="card-content">
내용
</div>
</div>
</div>
</div>
// ✅ 간결한 구조
<article className="card">
내용
</article>
특히 리스트를 렌더링할 때 이런 차이가 누적되면 체감할 수 있을 정도로 느려진다.
CSS 선택자 지옥
div가 많아지면 CSS 선택자도 복잡해진다. ▼
/* ❌ 뭘 선택하는지 알아보기 힘듦 */
.container > div > div:nth-child(2) > div > div {
/* ... */
}
/* ✅ 명확한 선택자 */
main > article .content {
/* ... */
}
복잡한 선택자는 브라우저가 스타일을 계산하는 속도도 느리게 만든다.
문제 5: 유지보수가 지옥이 된다
6개월 후의 나: "이게 뭐였더라?"
실제로 겪은 일이다. 6개월 전에 만든 컴포넌트를 수정하려고 열었는데... ▼
// 이런 코드를 보고 있으면...
<div className="wrapper">
<div className="inner">
<div className="box">
<div className="item">
<div className="content">
{/* 여기가 어디? */}
</div>
</div>
</div>
</div>
</div>
각 div가 뭘 하는지 파악하는 데만 30분이 걸렸다. 만약 이렇게 작성했다면? ▼
<section className="product-list">
<article className="product-card">
<header className="product-header">
{/* 아, 상품 카드의 헤더구나! */}
</header>
</article>
</section>
코드를 읽는 것만으로도 구조가 머릿속에 그려진다.
올바른 HTML 요소 선택하기
자주 쓰는 시맨틱 태그들
div 대신 써야 할 태그들과 언제 쓰는지 정리해봤다. ▼
<!-- 네비게이션 -->
<nav>
<ul>
<li><a href="/">Home</a></li>
</ul>
</nav>
<!-- 독립적인 콘텐츠 -->
<article>
<header>
<h2>제목</h2>
<time>2024-01-01</time>
</header>
<p>내용...</p>
</article>
<!-- 관련 콘텐츠 그룹 -->
<section>
<h2>관련 상품</h2>
<!-- ... -->
</section>
<!-- 부가 정보 -->
<aside>
<h3>광고</h3>
<!-- ... -->
</aside>
<!-- 페이지 하단 -->
<footer>
<p>© 2024</p>
</footer>
React에서 실전 적용
실제 React 컴포넌트에서 이렇게 활용한다. 이전보다는 구조가 명확히 이해된다. ▼
function ProductPage() {
return (
<main>
<header>
<h1>상품 목록</h1>
</header>
<section className="filters">
<h2>필터</h2>
{/* 필터 컴포넌트들 */}
</section>
<section className="products">
{products.map(product => (
<article key={product.id} className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>{product.description}</p>
<button>장바구니 담기</button>
</article>
))}
</section>
</main>
);
}
그래도 div를 써야 할 때
물론 `div`가 필요한 순간도 있다. 지금까지 `div`를 남발하지 말라는 것을 전달 한 것이지 사용하지 말라는 것은 아니다. 라이브러리가 요구하거나 스타일링을 위한 `wrapper`가 필요할 때는 `div`를 사용할 수 밖에 없다. ▼
// 스타일링을 위한 wrapper가 필요할 때
<article>
<div className="flex-wrapper"> {/* 이건 OK */}
<h2>제목</h2>
<time>날짜</time>
</div>
</article>
// 라이브러리가 요구할 때
<div ref={sliderRef}> {/* 어쩔 수 없는 경우 */}
{/* Swiper 같은 라이브러리 내용 */}
</div>
중요한 건 의미 없는 `div`를 남발하지 않는 것이다.
리팩토링 체크리스트
기존 div 수프를 개선하고 싶다면 이 체크리스트를 따라해보자. ▼
- 네비게이션 찾기: `<div className="nav">` → `<nav>`
- 헤더/푸터 변경: `<div className="header">` → `<header>`
- 리스트 구조화: 연속된 div들 → `<ul>` + `<li>`
- 제목 태그 사용: `<div className="title">` → `<h1>` ~ `<h6>`
- 링크는 링크답게: `<div onClick>` → `<a href>`
- 폼 요소 정리: `<div className="input-group">` → `<fieldset>`
- 독립 콘텐츠: 카드, 포스트 → `<article>`
마치며
이번 글에서는 div를 남발했을 때 생기는 문제들을 실제 경험과 함께 정리해봤다. 요약하자면 이렇다. ▼
- div 수프는 코드 가독성, 접근성, SEO, 성능, 유지보수 모든 면에서 악영향
- 시맨틱 HTML을 사용하면 코드만으로도 구조가 명확해짐
- div는 정말 의미가 없는 래퍼가 필요할 때만 사용
당장 동작하는데 뭐가 문제야? 라고 생각할 수도 있다. 맞다, 당장은 동작한다. 하지만 6개월 후 그 코드를 다시 열어봤을 때, 새로운 팀원이 합류했을 때, 접근성 감사를 받을 때, SEO 개선이 필요할 때... 그때 후회하지 않으려면 지금부터라도 올바른 HTML을 쓰는 습관을 들이자.
앞으로는 <div>를 타이핑하기 전에 잠시 멈추고 이게 정말 div가 맞나? 더 적절한 태그는 없을까?라고 자문해봐야겠다.
'Develop > Web' 카테고리의 다른 글
| [React][Error] tailwind css 설치 오류 (4) | 2025.10.05 |
|---|---|
| [NextJs] 하이아크 홈페이지 모노레포 적용기 (0) | 2025.09.23 |
| [React] 웹 프로젝트에서의 클린아키텍쳐 (0) | 2025.09.10 |
| [Web][Issue] 이중 인코딩으로 인한 사진 누락 (0) | 2025.07.17 |
| [Next.js][Develop] Next.js 클린 아키텍처 적용기 (0) | 2025.07.10 |