온페이지 점검 도구를 돌렸더니 H1 태그가 3개입니다라는 경고가 떴습니다. 처음에는 큰 문제라고 생각하지 않았습니다. 로고도 중요하고, 게시판 이름도 중요하고, 글 제목도 중요하다고 봤기 때문입니다.
하지만 실제 페이지 구조를 다시 보니 H1이 너무 많이 쓰이고 있었습니다. 사이트 로고, 게시판 이름, 글 제목이 모두 H1으로 출력되고 있었고, 검색엔진과 스크린 리더 입장에서는 이 페이지의 주제가 무엇인지 흐려질 수 있는 구조였습니다.
이 글은 H1을 무조건 하나만 써야 한다는 원칙만 설명하는 글이 아닙니다. 목록 페이지와 상세 페이지를 같은 템플릿이 처리하던 오래된 게시판에서, 어떤 화면에서는 게시판 이름을 H1로 두고 어떤 화면에서는 글 제목을 H1로 바꿨는지 정리한 글입니다.
H1이 세 개 나오던 실제 구조
문제가 된 상세 페이지의 HTML은 이런 형태였습니다.
<h1><img src="logo.png" alt="사이트 로고"></h1>
<h1>공지사항</h1>
<h1>2월 정기 점검 안내</h1>각각 사이트 로고, 게시판 이름, 글 제목이었습니다. 만든 사람 입장에서는 세 요소가 모두 중요해 보였을 수 있습니다.
하지만 페이지 단위로 보면 주제는 하나여야 합니다. 게시글 상세 페이지의 주제는 사이트 로고가 아니라 글 제목입니다. 게시판 이름은 상위 분류이고, 로고는 사이트 이동 링크에 가깝습니다.
H1이 여러 개 있다고 해서 무조건 검색엔진이 페이지를 이해하지 못하는 것은 아닙니다. 하지만 오래된 템플릿에서 로고, 메뉴명, 글 제목이 모두 같은 레벨로 출력되면 문서 구조가 흐려집니다.
로고를 H1에서 먼저 제외
가장 먼저 손댄 것은 로고였습니다. 로고는 모든 페이지에 반복되는 공통 요소입니다. 모든 페이지의 H1이 사이트명이나 로고가 되면, H1이 페이지별 주제를 알려주는 역할을 하지 못합니다.
기존 구조는 이런 식이었습니다.
<h1>
<a href="/"><img src="/img/logo.png" alt="예시 사이트"></a>
</h1>수정 후에는 헤딩 태그를 빼고 일반 로고 영역으로 바꿨습니다.
<div class="site-logo">
<a href="/"><img src="/img/logo.png" alt="예시 사이트"></a>
</div>로고에 필요한 것은 홈으로 가는 링크와 적절한 alt 텍스트입니다. 페이지 제목을 나타내는 H1까지 맡길 필요는 없었습니다.
목록과 상세를 같은 템플릿이 처리
진짜 원인은 템플릿 구조였습니다. 목록 페이지와 상세 페이지가 같은 파일에서 처리되고 있었습니다.
목록 페이지에서는 게시판 이름이 페이지 주제입니다.
공지사항 목록하지만 상세 페이지에서는 글 제목이 페이지 주제입니다.
2월 정기 점검 안내문제는 템플릿이 이 둘을 구분하지 않고 항상 게시판 이름을 H1로 출력하고, 상세 영역에서 글 제목도 다시 H1로 출력했다는 점입니다.
그래서 화면 종류에 따라 H1을 분기했습니다.
<?php
$isDetail = !empty($postNo);
$e = fn($v) => htmlspecialchars($v, ENT_QUOTES, 'UTF-8');
if ($isDetail) {
echo '<p class="board-name">' . $e($boardName) . '</p>';
echo '<h1 class="post-title">' . $e($postTitle) . '</h1>';
} else {
echo '<h1 class="board-name">' . $e($boardName) . '</h1>';
}
?>판단 기준은 단순하게 잡았습니다. 글 번호가 있으면 상세 페이지, 없으면 목록 페이지입니다.
URL보다 화면 상태 변수 기준
짧은 URL을 쓰는 사이트라면 경로로도 상세 여부를 판단할 수 있습니다.
<?php
// /site/board/856 → 목록
// /site/board/856/1226 → 상세
$isDetail = (bool) preg_match(
'#^/[^/]+/board/[0-9]+/[0-9]+#',
$_SERVER['REQUEST_URI']
);
?>하지만 저는 이 방식보다 $postNo 같은 화면 상태 변수를 기준으로 삼았습니다. URL 구조는 나중에 다시 바뀔 수 있고, 정규식이 여러 파일에 흩어지면 유지보수가 어려워지기 때문입니다.
화면이 이미 상세 글을 조회한 상태라면, URL을 다시 해석하기보다 애플리케이션에서 가진 값을 쓰는 편이 안정적이었습니다.
검색 결과 페이지의 H1도 별도 처리
목록과 상세만 고치면 끝일 줄 알았는데, 검색 결과 페이지에서 또 문제가 나왔습니다. 어떤 검색 결과 페이지는 H1이 0개였습니다.
검색 결과 화면은 게시판 목록과 비슷해 보이지만, 실제 주제는 다릅니다. 단순히 게시판 이름만 H1로 두면 사용자가 어떤 결과를 보고 있는지 흐려질 수 있습니다.
그래서 검색어가 있는 경우에는 별도 H1을 출력했습니다.
<?php
if (!empty($keyword)) {
echo '<h1 class="search-title">' . $e($keyword) . ' 검색 결과</h1>';
} elseif ($isDetail) {
echo '<p class="board-name">' . $e($boardName) . '</p>';
echo '<h1 class="post-title">' . $e($postTitle) . '</h1>';
} else {
echo '<h1 class="board-name">' . $e($boardName) . '</h1>';
}
?>이렇게 나누면 화면별 H1이 명확해집니다.
게시판 목록 → 게시판 이름
게시글 상세 → 글 제목
검색 결과 페이지 → 검색어 검색 결과헤딩 레벨 순서 점검
H1 개수를 고친 뒤에는 H2, H3 구조도 같이 봤습니다. H1 다음에 바로 H4가 나오거나, 단순히 글자 크기를 키우려고 H3를 쓰는 부분이 있었습니다.
좋지 않은 구조는 이런 식입니다.
<h1>2월 정기 점검 안내</h1>
<h4>점검 시간</h4>
<h4>영향 범위</h4>헤딩은 디자인 크기가 아니라 문서 구조입니다. H1 아래에는 H2, H2 아래에는 H3처럼 단계가 자연스럽게 내려가는 편이 좋습니다.
수정 후에는 이런 형태로 맞췄습니다.
<h1>2월 정기 점검 안내</h1>
<h2>본문 요약</h2>
<h2>점검 내용</h2>
<h3>점검 시간</h3>
<h3>영향 범위</h3>글자 크기나 굵기는 CSS로 조정했습니다.
.post-title {
font-size: 28px;
line-height: 1.35;
}
.section-title {
font-size: 20px;
font-weight: 700;
}브라우저 콘솔로 H1 개수 확인
수정 후에는 눈으로만 보지 않고 브라우저 콘솔에서 직접 셌습니다.
document.querySelectorAll('h1').length기대값은 화면마다 같았습니다.
목록 페이지 → 1
상세 페이지 → 1
검색 결과 페이지 → 1H1 내용도 같이 확인했습니다.
[...document.querySelectorAll('h1')].map(el => el.textContent.trim())이렇게 보면 어떤 요소가 H1로 잡혔는지 바로 알 수 있습니다. 로고 이미지가 H1 안에 남아 있으면 텍스트가 비거나 alt만 보이는 식으로 티가 납니다.
title과 meta description도 함께 분기
H1을 고치다 보니 title과 meta description도 같은 문제가 있었습니다. 목록과 상세가 같은 템플릿을 쓰다 보니, 상세 페이지에서도 목록용 제목이나 기본 설명이 출력되는 경우가 있었습니다.
그래서 H1 분기 기준을 title과 description에도 같이 적용했습니다.
<?php
if ($isDetail) {
$pageTitle = $postTitle . ' | ' . $siteName;
$metaDesc = mb_substr(trim(strip_tags($postBody)), 0, 100, 'UTF-8');
} elseif (!empty($keyword)) {
$pageTitle = $keyword . ' 검색 결과 | ' . $siteName;
$metaDesc = $keyword . ' 검색 결과 목록입니다.';
} else {
$pageTitle = $boardName . ' | ' . $siteName;
$metaDesc = $boardName . ' 게시글 목록입니다.';
}
?>검색엔진이 보는 제목, 설명, 본문 H1이 서로 같은 방향을 가리키도록 맞춘 것입니다.
적용 후 달라진 점
수정 후에는 온페이지 점검 도구의 헤딩 경고가 사라졌습니다. 더 중요한 것은 페이지 구조가 훨씬 분명해졌다는 점입니다.
- 로고 H1 제거
- 목록 페이지는 게시판 이름만 H1로 출력
- 상세 페이지는 글 제목만 H1로 출력
- 검색 결과 페이지도 검색어 기준 H1 출력
- H2, H3 순서 정리
- title과 meta description 분기 기준 통일
스크린 리더로 페이지를 훑을 때도 주제를 파악하기 쉬워졌습니다. 검색엔진뿐 아니라 사람에게도 페이지 구조가 더 자연스러워진 셈입니다.
H1 점검 체크리스트
H1은 페이지 주제를 보여주는 자리
H1은 장식용 태그가 아니라 이 페이지가 무엇에 관한 것인지 알려주는 자리입니다. 로고, 게시판 이름, 글 제목이 모두 H1이면 어느 것도 제대로 강조되지 않습니다.
목록과 상세를 같은 파일이 처리하는 구조에서는 이런 문제가 자주 생깁니다. 겉으로는 같은 게시판 화면이지만, 검색엔진과 사용자에게 전달해야 하는 주제는 다릅니다.
제가 얻은 기준은 단순합니다. 목록에서는 게시판 이름, 상세에서는 글 제목, 검색 결과에서는 검색어를 중심으로 H1을 출력합니다. 그리고 로고는 H1이 아니라 홈으로 가는 링크와 alt 텍스트만 맡기면 충분합니다.
H1을 정리할 때 title, canonical, meta description도 같이 확인하면 좋습니다. 같은 분기 기준을 쓰는 요소들이라 한 번에 맞춰두면 이후 SEO 점검이 훨씬 쉬워집니다.