タイトル: <head>タグに書いた内容が<body>タグに移動する場合の解決方法
SEOタイトル: <head> の内容が <body> に移動する原因と対処(HTML5 パーサのエラー復旧)
| この記事の要点 |
|
現象
HTML ソースでは <head> 内に書いたはずの <meta> / <link> / <script> が、ブラウザの DevTools で見ると <body> 内に移動している現象。Chrome / Firefox / Safari 共通で起きます。
<!-- 書いたソース -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My Page</title>
<div>これが原因!</div> <!-- ← head に div は置けない -->
<link rel="stylesheet" href="style.css">
<script src="app.js"></script>
</head>
<body>
<h1>Hello</h1>
</body>
</html><!-- ブラウザが実際に解釈した DOM (Chrome DevTools で見える) -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My Page</title>
<!-- ← link と script が消えた -->
</head>
<body>
<div>これが原因!</div> <!-- ← head にあった div が body 先頭へ -->
<link rel="stylesheet" href="style.css"> <!-- ← 一緒に押し出された -->
<script src="app.js"></script>
<h1>Hello</h1>
</body>
</html>
原因: HTML5 パーサのエラー復旧
HTML5 仕様(WHATWG HTML Living Standard)はパースエラー時の挙動を厳密に決めており、 内に不正な要素が現れた瞬間に、その要素と以降の要素はすべて へ移動するルールです。これにより「壊れた HTML でも何かは表示される」性質を担保しています。
に置けるタグ一覧
| タグ | 用途 |
|---|---|
<title> | タブのタイトル(必須) |
<meta> | charset / viewport / description / og:* 等 |
<link> | CSS / favicon / preload / canonical |
<script> | 外部 / インライン JS |
<style> | インライン CSS |
<base> | 相対 URL の基底 |
<noscript> | JS 無効時表示(head 内は <link>/<style>/<meta> 限定) |
<template> | クライアントテンプレ(head/body 両方可) |
これ以外(<div> / <p> / <span> / 素のテキスト)を置くと押し出されます。
原因チェックリスト
① タグの誤配置
<!-- NG パターン -->
<head>
<title>Page</title>
Hello <!-- ← 素のテキスト NG -->
<div class="header">...</div> <!-- ← div NG -->
<p>説明</p> <!-- ← p NG -->
<h1>Title</h1> <!-- ← h1 NG -->
<header>...</header> <!-- ← header NG (HTML5) -->
</head>
② BOM や空白行
NG: ファイル先頭に BOM (EF BB BF) や空白行がある
↓
<!DOCTYPE html> ← BOM があると quirks mode に落ちる
<html>
対処:
- エディタで「BOM なし UTF-8」で保存し直す
- VS Code: 右下のステータスバーで「UTF-8 with BOM」→「UTF-8」に変更
- PHP: <?php の前に空白や改行を入れない(include 元も同様)
③ サーバ側テンプレートの出力ミス
<?php
// Laravel Blade / 素 PHP で起きやすい例
// NG: <?php タグの直前に空白
<?php $title = 'My Page'; ?>
// NG: include されたファイルの末尾に空白行があり、それが <head> 内に流れ込む
// NG: BOM 付きファイル
// → output buffer 取って一括出力するか、文字コード変換ツールで一括解消
Laravel Blade の場合:
{{-- NG: @section の外にテキストや HTML を書いている (head に流れ込む可能性) --}}
@extends('layout')
これは body 用テキスト {{-- ← レイアウト次第で <head> 内に出る --}}
@section('content')
<h1>Hello</h1>
@endsection
{{-- OK: 必ず @section 内で出力 --}}
@extends('layout')
@section('content')
<h1>Hello</h1>
@endsection
④ の位置
<meta charset="UTF-8"> は 開始から 1024 バイト以内に置く必要があります。先に長いコメントや別 meta があると charset が無視され、エラー復旧が走ることがあります。
<!-- OK -->
<head>
<meta charset="UTF-8"> <!-- 最初に -->
<meta name="viewport" content="...">
<title>...</title>
...
</head>
確認手順
- Chrome DevTools の Sources タブで生 HTML を確認(サーバが何を返したか)
- 同じく Elements タブで実 DOM を確認
- 2 つを diff すると押し出されたタグが特定可能
- 生 HTML の
<head>内に置けないタグが無いか目視 - W3C Markup Validator に通す
# CLI で W3C 系チェック
npm install -g html-validate
html-validate index.html
# あるいは tidy
sudo apt install tidy
tidy -errors -quiet index.html
対処パターン
| 原因 | 対処 |
|---|---|
| head 内に div / p / 素テキスト | 削除 or body 側へ移動 |
| BOM 付きファイル | 「BOM なし UTF-8」で保存 |
| テンプレ include の末尾改行 | 末尾の ?> を省略、ob_start 利用 |
| meta charset が遅い | head 開始直後に置く |
| 独自タグ(カスタム要素) | HTML5 でカスタム要素は body 推奨。head に置くとパースエラー |
| コメントタグの誤閉じ | <!-- ... --> 構造を再確認 |
FAQ
Q: head に Google Analytics の <script> を置きたいだけなのに body に行く
A: <script> 自体は head 可。直前に不正タグがあって連鎖押し出しされている。直前を確認。
Q: jQuery プラグインが <link> を動的に head に追加してくれない
A: document.head.appendChild(...) なら確実。jQuery $('head').append(...) はパース後の DOM 操作なので問題なし(パース時の押し出しと別の話)。
Q: View Source(生 HTML)と DevTools で差があるのが普通?
A: 動的 DOM 操作(JS)の影響もあるが、JS なしで読み込み直後に差がある場合はパーサ復旧。View Source は壊れた HTML、Elements は復旧後の DOM を表示。