head 태그 순서와 SEO | canonical 위치·메타태그 배치 기준

canonical 태그는 head 위쪽에 있어야 한다는 글을 여러 번 봤습니다. 어떤 글은 title 앞에 두면 안 된다고 하고, 어떤 글은 description보다 먼저 두는 것이 좋다고 했습니다.

처음에는 순서 자체가 랭킹에 영향을 주는 줄 알았습니다. 그래서 title, canonical, description의 위치를 바꿔보며 확인했습니다. 결론은 조금 달랐습니다.

head 안에 정상적으로 들어가기만 하면 canonical의 정확한 순서가 직접적인 순위 요소처럼 작동하지는 않았습니다. 다만 순서를 정해두는 것은 매우 중요했습니다. 순서는 랭킹 공식이 아니라 실수를 줄이는 운영 규칙에 가까웠습니다.

head 태그 순서와 canonical·메타태그 배치 코드 예제

canonical 위치보다 head 안에 있는지 먼저 확인

가장 먼저 확인할 것은 canonical이 몇 번째 줄에 있는지가 아니었습니다. 실제 파싱 결과에서 <head> 안에 남아 있는지였습니다.

소스 보기에서는 정상처럼 보여도 브라우저가 HTML을 파싱한 결과는 다를 수 있습니다.

<head>
  <title>페이지 제목</title>
  <div>잘못 들어간 태그</div>
  <link rel="canonical" href="https://example.com/site/board/856">
</head>

head 안에 허용되지 않는 태그가 들어가면 브라우저가 head를 예상보다 빨리 닫을 수 있습니다. 그러면 뒤에 있던 canonical이 body 쪽으로 밀릴 수 있습니다.

이 경우 문제는 canonical이 title보다 앞이냐 뒤냐가 아닙니다. canonical이 head 밖으로 밀렸다는 점이 문제입니다.

소스 보기보다 개발자 도구 확인

이 문제는 소스 보기만으로는 놓칠 수 있습니다. Ctrl+U로 보는 것은 서버가 보낸 원본 HTML입니다. 브라우저가 실제로 해석한 DOM 구조와 다를 수 있습니다.

그래서 확인은 개발자 도구 Elements 탭에서 했습니다.

1. 페이지 열기
2. 개발자 도구 열기
3. Elements 탭에서 <head> 펼치기
4. canonical, description, robots가 head 안에 있는지 확인

콘솔에서도 확인할 수 있습니다.

document.head.querySelector('link[rel="canonical"]')?.href

값이 나오더라도 반드시 원하는 주소인지 확인해야 합니다. 태그가 존재하는 것과 값이 맞는 것은 다른 문제였습니다.

제가 정한 head 태그 순서

테스트 후에는 아래 순서로 정했습니다.

<head>
  <meta charset="UTF-8">
  <base href="https://example.com/">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <title>게시판 이름 | 사이트명</title>
  <link rel="canonical" href="https://example.com/site/board/856">
  <meta name="description" content="게시판 설명 문장">
  <meta name="robots" content="index,follow">

  <meta property="og:type" content="article">
  <meta property="og:title" content="게시글 제목">
  <meta property="og:description" content="게시글 요약문">
  <meta property="og:image" content="https://example.com/img/thumb.jpg">
  <meta property="og:url" content="https://example.com/site/board/856">

  <meta name="twitter:card" content="summary_large_image">

  <link rel="stylesheet" href="/css/style.css">
  <script src="/js/app.js" defer></script>
</head>

이 순서는 정답이라기보다 제 사이트에서 실수를 줄이기 위한 기준이었습니다. 중요한 SEO 태그를 흩어두지 않고 한 묶음으로 모으는 것이 목적이었습니다.

charset과 base를 앞에 둔 이유

charset은 가능하면 가장 앞쪽에 두었습니다.

<meta charset="UTF-8">

문자 인코딩 선언이 늦게 나오면 브라우저가 앞부분을 다시 해석해야 할 수 있습니다. 특히 오래된 사이트에서 한글이 섞인 title이나 description을 다룰 때는 먼저 선언하는 편이 안전했습니다.

base 태그를 쓰는 페이지에서는 위치가 더 중요했습니다.

<base href="https://example.com/">

base는 상대경로 해석에 영향을 줍니다. 따라서 상대경로로 쓰는 stylesheet, script, image, link 태그보다 앞에 있어야 합니다.

다만 base 태그는 사이트 전체에 영향을 줄 수 있어서 필요한 페이지에만 조건부로 출력했습니다.

title·canonical·description을 묶어둔 이유

검색엔진과 직접 관련된 태그는 한곳에 모았습니다.

<title>게시판 이름 | 사이트명</title>
<link rel="canonical" href="https://example.com/site/board/856">
<meta name="description" content="게시판 설명 문장">
<meta name="robots" content="index,follow">

이렇게 묶어두면 템플릿을 볼 때 빠진 태그가 바로 보입니다. title은 바뀌었는데 canonical이 예전 값이거나, description은 자동 생성됐는데 robots가 빠지는 실수를 줄일 수 있습니다.

순서 자체가 점수를 주는 것은 아니지만, 태그 관리가 쉬워지면 결과적으로 SEO 오류가 줄어듭니다.

canonical에서 더 중요한 값의 정확성

canonical은 위치보다 값이 훨씬 중요했습니다. 잘못된 canonical은 페이지 전체 신호를 엉뚱한 곳으로 보낼 수 있습니다.

실수예시문제
모든 페이지가 메인을 가리킴https://example.com/하위 페이지 색인 신호가 약해짐
상대경로 사용/board/856base에 따라 다르게 해석될 수 있음
http/https 혼용http://example.com/page중복 주소 신호가 남음
캐시된 값 출력다른 글의 canonical엉뚱한 글이 대표로 잡힐 수 있음

그래서 canonical은 항상 절대 URL로 출력했습니다.

<?php
$canonical = 'https://example.com' . $canonicalPath;

echo '<link rel="canonical" href="'
   . htmlspecialchars($canonical, ENT_QUOTES, 'UTF-8')
   . '">' . "\n";
?>

프로토콜, 호스트, 경로까지 포함한 완전한 주소를 만드는 것이 기준이었습니다.

페이지 유형별 canonical 생성

같은 템플릿을 목록, 상세, 검색 페이지가 함께 쓰고 있었기 때문에 canonical도 페이지 유형별로 나눴습니다.

<?php
$base = 'https://example.com/' . rawurlencode($siteId);

if ($pageType === 'post') {
    $canonical = $base . '/board/' . (int)$boardNo . '/' . (int)$postNo;
} elseif ($pageType === 'search') {
    $canonical = $base . '/board/' . (int)$boardNo . '/search'
               . '?keyword=' . rawurlencode($keyword);
} else {
    $canonical = $base . '/board/' . (int)$boardNo
               . ($start > 0 ? '?start=' . (int)$start : '');
}
?>

목록은 목록 자기 자신, 상세는 상세 자기 자신, 검색 결과는 검색 결과 자기 자신을 가리키게 했습니다. 내용이 다른 페이지를 억지로 하나의 canonical에 묶지 않았습니다.

og:url도 canonical과 맞추기

canonical을 정리하면서 og:url도 같이 봤습니다. 카카오톡이나 SNS 공유에서 쓰이는 URL이 canonical과 다르면 공유 미리보기와 검색엔진 기준 주소가 어긋날 수 있습니다.

<?php
echo '<link rel="canonical" href="'
   . htmlspecialchars($canonical, ENT_QUOTES, 'UTF-8')
   . '">' . "\n";

echo '<meta property="og:url" content="'
   . htmlspecialchars($canonical, ENT_QUOTES, 'UTF-8')
   . '">' . "\n";
?>

두 태그의 역할은 다르지만, 대표 URL이라는 관점에서는 같은 값을 쓰는 편이 관리하기 쉬웠습니다.

잘못된 head 조각 제거

오래된 템플릿에서는 head 안에 들어가면 안 되는 조각도 있었습니다.

<head>
  <title>페이지 제목</title>
  <div class="hidden">공통 배너</div>
  <script>document.write('<meta name="robots" content="index,follow">');</script>
</head>

이런 구조는 정리했습니다. head에는 메타데이터와 리소스 선언만 두고, 화면에 보이는 요소는 body로 옮겼습니다.

<head>
  <title>페이지 제목</title>
  <link rel="canonical" href="https://example.com/page">
</head>
<body>
  <div class="hidden">공통 배너</div>
</body>

head가 깨지면 태그 순서를 논하기 전에 기본 구조가 무너집니다.

배포 후 한 줄 점검

배포 후에는 주요 페이지의 canonical을 한 번에 확인했습니다.

for u in / /site /site/board/856 /site/board/856/1226 /site/board/856/search?keyword=테트리스; do
  echo "== $u"
  curl -s "https://example.com$u" | grep -o '<link[^>]*canonical[^>]*>'
done

기대 결과는 페이지마다 서로 다른 canonical이 나오는 것입니다.

/                              → https://example.com/
/site                          → https://example.com/site
/site/board/856                 → https://example.com/site/board/856
/site/board/856/1226            → https://example.com/site/board/856/1226
/site/board/856/search?...      → https://example.com/site/board/856/search?keyword=...

모든 페이지가 같은 canonical을 출력하면 템플릿에 하드코딩이 남아 있는 것입니다.

개발자 도구에서 DOM 기준 확인

서버 응답만 확인하고 끝내지 않았습니다. 브라우저가 실제로 만든 DOM에서도 확인했습니다.

[...document.head.querySelectorAll('title, link[rel="canonical"], meta[name="description"], meta[name="robots"]')]
  .map(el => el.outerHTML)

이 코드로 title, canonical, description, robots가 head 안에 어떤 순서로 들어갔는지 볼 수 있었습니다.

그리고 body로 밀려난 canonical이 없는지도 확인했습니다.

document.body.querySelector('link[rel="canonical"]')

null이 나오면 body 안에는 canonical이 없다는 뜻입니다.

적용 후 달라진 점

head 태그 순서를 정리한 뒤에는 문제가 한 번에 사라졌다기보다, 실수를 찾기가 쉬워졌습니다.

  • canonical이 head 밖으로 밀리는 구조 확인
  • title, canonical, description, robots를 한 묶음으로 관리
  • 페이지 유형별 canonical 값 오류 감소
  • og:url과 canonical 값 불일치 감소
  • 템플릿 하드코딩 canonical 발견
  • 배포 후 점검 명령어 고정

순서 자체가 랭킹을 올려준 것은 아니었습니다. 하지만 순서를 정해두니 빠진 태그와 잘못된 값이 더 빨리 보였습니다.

head 태그 점검 체크리스트

순서는 점수보다 실수 방지에 가깝다

head 태그 순서는 랭킹을 올리는 숨은 버튼은 아니었습니다. canonical이 title보다 한 줄 위에 있다고 더 잘 색인되고, description보다 뒤에 있다고 순위가 떨어지는 식으로 보이지는 않았습니다.

하지만 순서를 정해두는 것은 의미가 있었습니다. 중요한 태그가 흩어져 있으면 빠진 태그를 놓치기 쉽고, 템플릿 조각이 섞이면서 head 밖으로 밀리는 문제도 발견하기 어렵습니다.

제가 얻은 기준은 단순합니다. charset과 base처럼 파싱에 영향을 주는 태그는 앞에 두고, title·canonical·description·robots는 한 묶음으로 관리합니다. 공유용 OG 태그는 그다음, CSS와 JS는 뒤쪽에 둡니다.

결국 head 태그 순서는 SEO 공식이라기보다 유지보수 규칙이었습니다. 규칙을 정해두면 실수가 줄고, 실수가 줄면 검색엔진이 페이지를 더 안정적으로 이해할 수 있었습니다.


관련 글

댓글 남기기