HTML を書き始めるとき、エディタの補完機能や雛形から出てきた <!DOCTYPE html> や <head> を、意味が分からないまま毎回コピーして使っている、という方は少なくありません。しかしこの数行は飾りではなく、ブラウザーがページをどう解釈するかを決める土台になっています。この記事では、HTML ファイルの最小構成を上から順にたどりながら、<!DOCTYPE html>・<html>・<head>・<body> がそれぞれ何をしているのか、そして書き忘れると何が起こるのかを解説します。
目次
HTML ファイルの最小構成
まずは、実務で「これ以上は削れない」といえる最小の HTML ファイルを見てください。ここから中身を足していくことになります。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>はじめてのページ</title>
</head>
<body>
<h1>はじめてのページ</h1>
<p>ここが画面に表示される内容です。</p>
</body>
</html>
この 12 行を上から読むと、HTML ファイルの構造がそのまま順番に並んでいることが分かります。最初の 1 行がブラウザーへの宣言、次にすべてを包む <html>、その中が「表示されない情報」の <head> と「表示される内容」の <body> の 2 つに分かれる、という形です。
| 書くもの | 種類 | 役割 |
|---|---|---|
<!DOCTYPE html> | 文書型宣言 | この文書を HTML の標準モードで解釈するようブラウザーに伝える |
<html> | 要素 | 文書全体を包むルート要素。lang で言語を指定する |
<head> | 要素 | タイトル・文字コード・CSS など、画面に表示されない情報を置く |
<body> | 要素 | 見出しや文章、画像など、画面に表示される内容を置く |
いちばん上の <!DOCTYPE html> だけ「要素」ではなく「文書型宣言」と書いてある点に注目してください。ここが最初のつまずきどころなので、順番に見ていきます。
<!DOCTYPE html> はタグではなく文書型宣言
<!DOCTYPE html> は、山かっこで囲まれているので一見タグのように見えますが、HTML の要素(タグ)ではありません。これは 文書型宣言(DOCTYPE、Document Type Declaration)と呼ばれる、HTML の文法とは別枠の 1 行です。
要素ではないので、次のような性質があります。
まず、終了タグがありません。</!DOCTYPE> のような書き方は存在しませんし、<!DOCTYPE html /> のように閉じる必要もありません。そして、中に何かを入れることもできません。属性を付けることもなく、HTML5 では常にこの 1 行をそのまま書くだけです。DOCTYPE の部分の大文字・小文字は区別されないため <!doctype html> でも同じ意味になりますが、慣習として大文字で書かれることがほとんどです。
また、必ずファイルの先頭に書きます。ここより前に文字(テキスト)が出力されてしまうと、ブラウザーは「DOCTYPE が無い文書」として扱ってしまいます。PHP などで HTML を出力しているときに、うっかり冒頭で改行や空白以外の文字を吐いてしまうケースがこれにあたります。
HTML5 で 1 行に短くなった
「なぜこんな独特な書き方なのか」という疑問は、HTML5 より前の DOCTYPE を見ると理解しやすくなります。かつての HTML4 や XHTML では、次のような長い宣言を書いていました。
<!-- HTML 4.01 Strict -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<!-- XHTML 1.0 Transitional -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
当時の DOCTYPE は、その文書がどの規格のどのバリエーション(Strict / Transitional / Frameset)に従うのかを、DTD と呼ばれる定義ファイルの URL つきで示すものでした。とても覚えられる長さではないので、コピーして使うのが当たり前でしたし、うっかり途中で改行位置を間違えて動作が変わることもありました。
HTML5 では、この「規格を細かく指定する」という考え方そのものをやめました。現在の DOCTYPE に残っている役割は、実質的に「この文書を標準モードで解釈してください」とブラウザーに伝えることだけです。だから中身が必要なくなり、<!DOCTYPE html> という最短の 1 行になりました。バージョン番号が書かれていないのも、そのためです。
DOCTYPE を書き忘れると起きること
DOCTYPE を書かなくてもページは表示されます。エラーにもなりません。だからこそ厄介で、「なぜか CSS の効き方がおかしい」という形で症状が出ます。
ブラウザーが互換モードに切り替わる
ブラウザーは DOCTYPE の有無を見て、レンダリングの方式を切り替えます。DOCTYPE がある場合は仕様どおりに解釈する標準モード(standards mode)、無い場合は昔のブラウザーの挙動を再現する互換モード(quirks mode、クワークスモード)です。
互換モードが用意されている理由は、後方互換性です。DOCTYPE という概念が広まる前に作られた大量のページは、当時のブラウザーの独自解釈を前提に組まれていました。それらを最新のブラウザーが仕様どおりに描画すると、レイアウトが総崩れになってしまいます。そこで「DOCTYPE が無い=古い時代のページだろう」と判断して、当時の解釈を再現するようになっているわけです。
いちばん影響が大きいのはボックスモデル
互換モードでの違いはいくつもありますが、実務で最初に気付くのは width と height の解釈です。標準モードでは、CSS の width は内容領域だけの幅を指し、padding と border はその外側に加算されます。ところが互換モードでは、width に padding と border が含まれるという古い解釈になります。
.box {
width: 200px;
padding: 20px;
border: 1px solid #333;
}
/* 標準モード: 実際の見た目の幅は 200 + 20*2 + 1*2 = 242px */
/* 互換モード: 実際の見た目の幅は 200px(内容領域は 158px になる) */
同じ CSS なのに要素の幅が 42px ずれるので、カラムが折り返したり、はみ出したりします。「自分の環境だけレイアウトが崩れる」「ローカルでは崩れないのに公開したら崩れた」といったときは、まず DOCTYPE の有無を疑ってください。
どちらのモードかを確認する方法
いま表示しているページがどちらのモードなのかは、ブラウザーの開発者ツールのコンソールで document.compatMode を実行すれば確認できます。
document.compatMode;
// "CSS1Compat" → 標準モード(DOCTYPE が効いている)
// "BackCompat" → 互換モード(DOCTYPE が無い・壊れている)
"BackCompat" が返ってきたら、DOCTYPE が書かれていないか、その前に何かが出力されている可能性が高い状態です。ブラウザーで「ページのソースを表示」して、本当に 1 行目が <!DOCTYPE html> になっているかを確認しましょう。エディタ上のファイルではなく、実際にサーバーから返ってきた HTML を見るのがポイントです。
<html> はすべてを包むルート要素
DOCTYPE の次に来る <html> は、文書のいちばん外側にある要素で、ルート要素と呼ばれます。HTML ファイルに書かれる要素は、DOCTYPE とコメントを除いてすべてこの中に入ります。1 つの文書に 1 つだけ存在し、子要素は <head> と <body> の 2 つだけ、という決まりです。
JavaScript から見ると document.documentElement がこの要素にあたり、CSS では :root 疑似クラスで選択できます。カスタムプロパティ(CSS 変数)の定義を :root に書くのが定番なのは、ここが文書の最上位だからです。
そして <html> には、lang 属性で文書の言語を指定します。日本語のページなら lang="ja" です。この指定はスクリーンリーダーがどの言語の音声で読み上げるかの判断や、ブラウザーの翻訳機能、日本語と中国語で字形が異なる漢字の表示などに影響します。<html> に指定しておけば、文書全体の既定の言語として子要素すべてに引き継がれます。
<!-- 日本語のページ -->
<html lang="ja">
<!-- 英語のページ -->
<html lang="en">
指定できる値や、一部だけ別の言語にしたいときの書き方については、lang 属性を扱った記事で詳しく解説しています。ここでは「ルート要素には言語を書く」とだけ覚えておけば十分です。
<head> は画面に表示されない情報を書く場所
<head> は、その文書についての情報(メタデータ)を置く場所です。ここに書いた内容は、原則としてページ本文としては画面に表示されません。読者に見せる文章ではなく、ブラウザーや検索エンジン、SNS などの「機械」に向けた情報を並べる場所だと考えてください。
<head> の中に書ける要素は決まっており、次のものに限られます。
| 要素 | 役割 |
|---|---|
<title> | 文書のタイトル。必須で、1 文書に 1 つだけ |
<meta> | 文字コード・ビューポート・説明文など、さまざまな文書情報 |
<link> | スタイルシートやファビコンなど、外部リソースとの関連付け |
<style> | CSS をファイル内に直接書く |
<script> | JavaScript の読み込み・記述 |
<base> | 相対 URL の基準となる URL を指定する |
<noscript> | JavaScript が無効な環境向けの代替内容 |
<template> | あとから JavaScript で複製して使うマークアップの雛形 |
逆に言えば、<h1> や <p>、<div> のような表示用の要素を <head> の中に書くことはできません。書いてしまった場合、ブラウザーは「ここから <body> が始まったのだろう」と解釈して、その要素以降を <body> に移してしまいます。エラーにはならないので、意図と違う場所に要素が入っていることに気付きにくいのが厄介なところです。
<title> は省略できない必須要素
<head> の中身の多くは省略可能ですが、<title> だけは省略できません。HTML の仕様上、通常のページでは <head> の中に <title> をちょうど 1 つ含める必要があると定められています。バリデーター(HTML の文法チェックツール)にかけると、<title> が無いファイルはエラーとして報告されます。
必須になっているのは、この 1 行が多くの場所で「そのページの名前」として使われるからです。
ブラウザーのタブに表示されるのはもちろん、ブックマークに登録したときの既定の名前、履歴の一覧、そして検索結果に並ぶリンクのテキストも、基本的にはこの <title> の内容です。つまり、読者がページを開く前に目にする唯一のテキストであり、書かなければブラウザーはファイル名や URL をそのまま表示することになります。
<head>
<meta charset="UTF-8">
<!-- 「ページ名 | サイト名」のように書くのが一般的 -->
<title>お問い合わせ | サンプル商店</title>
</head>
なお、<title> の中にはテキストしか書けません。<strong> などのタグを入れても要素としては解釈されず、そのまま文字として扱われます。
文字コードの指定は head の先頭付近に書く
<meta charset="UTF-8"> は、このファイルがどの文字コードで保存されているかをブラウザーに伝える 1 行です。<head> を開いたらまずこれを書く、というのが定番になっています。
先頭に置くことには明確な理由があります。ブラウザーは HTML を読み込むとき、まだ文字コードが分からない状態で先頭からバイト列を読み進めていき、文字コードの指定を見つけた時点で「では、この符号化方式で解釈しよう」と決めます。もし文書のずっと後ろにその指定があると、それまで読んだ部分を別の解釈でやり直すことになり、無駄が生じます。
そのため HTML の仕様では、文字コードの宣言は文書の先頭から 1024 バイト以内に完全に含まれていなければならないと定められています。1024 バイトというのは意外と短く、<head> に SNS 用の <meta> や長い説明文を先に並べていると、あっさり超えてしまいます。迷う余地をなくすためにも、<head> の 1 行目に書いてしまうのが確実です。
<head>
<!-- 何よりも先に書く -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>サンプル商店</title>
</head>
<meta> にはこのほかにも viewport や description など多くの用途がありますが、それぞれの詳細は meta 要素を扱った記事に譲ります。ここでは「文字コードは <head> の先頭」とだけ押さえてください。
CSS と JavaScript の読み込み
CSS ファイルの読み込みも <head> の仕事です。<link rel="stylesheet"> で外部ファイルを読み込むか、<style> で直接 CSS を書きます。表示の指示であってページの内容ではないので、<head> に置くのが原則です。
<head>
<meta charset="UTF-8">
<title>サンプル商店</title>
<!-- 外部の CSS ファイルを読み込む -->
<link rel="stylesheet" href="/css/style.css">
<!-- ファイル内に直接書くこともできる -->
<style>
body { margin: 0; }
</style>
<!-- JavaScript は defer を付けて head に置くのが扱いやすい -->
<script src="/js/main.js" defer></script>
</head>
<script> だけは少し事情が違い、<head> にも <body> にも書けます。これは「表示されるかどうか」ではなく「いつ実行されるか」で置き場所が変わるためです。<head> にそのまま置くと HTML の解析が止まってしまうので、defer を付けて <head> に置くか、</body> の直前に置くのが一般的です。この使い分けは async / defer を扱った記事で詳しく解説しています。
<body> は画面に表示される内容を書く場所
<body> は、実際に画面に描画される内容を入れる要素です。見出し、段落、画像、リンク、フォーム、テーブルといった要素はすべてこの中に書きます。ブラウザーのウィンドウに映っているものは、基本的にすべて <body> の中身だと考えて構いません。
1 つの文書に <body> は 1 つだけです。<html> の 2 番目の子要素として、<head> の直後に置きます。JavaScript からは document.body で参照できます。
<body>
<header>
<h1>サンプル商店</h1>
</header>
<main>
<p>当店の商品をご紹介します。</p>
</main>
<footer>
<p>© 2026 サンプル商店</p>
</footer>
</body>
この <body> の中をどう組み立てるか、つまり <header> や <main> といった要素をどう使い分けるかは、セマンティックタグを扱った記事のテーマです。この記事では「表示される内容はすべてここに入る」という位置づけだけ押さえてください。
なお、<body> 自体も CSS の対象になる要素です。ページ全体の背景色や既定のフォントを body { ... } で指定するのは、この要素が表示領域全体を受け持っているからです。WordPress のように、テーマ側が <body> にクラスを出力してページの種類ごとにスタイルを切り替える仕組みもあります。
head と body のどちらに書くか迷ったとき
判断の基準はひとつだけです。その内容がページ本文として画面に表示されるものかどうかを考えます。表示されるなら <body>、表示されないなら <head> です。
紛らわしいのは「画面のどこかには出るが、ページの中身ではない」ものです。たとえば <title> の文字列はブラウザーのタブに表示されますが、ページの本文としては表示されません。これはブラウザーの UI に渡す情報なので <head> に入ります。ファビコンも同様で、タブに絵が出ますが、指定するのは <head> の <link> です。
| 書きたいもの | 置く場所 | 理由 |
|---|---|---|
ページのタイトル(<title>) | head | タブや検索結果に出るが、本文としては表示されない |
ページの見出し(<h1>) | body | 画面に文字として表示される |
文字コード・説明文(<meta>) | head | ブラウザーや検索エンジンへの情報 |
スタイルシートの読み込み(<link>) | head | 表示方法の指示であって表示物ではない |
ファビコン(<link rel="icon">) | head | ブラウザー UI 向けの指定 |
画像(<img>) | body | 画面に表示される内容そのもの |
JavaScript(<script>) | head / body | 表示物ではないが、実行タイミングで置き場所を選ぶ |
もうひとつ覚えておきたいのは、間違えてもブラウザーはエラーを出さず、勝手に配置を直してしまうということです。次のコードを見てください。
<head>
<meta charset="UTF-8">
<h1>サンプル商店</h1> <!-- head には書けない -->
<title>サンプル商店</title> <!-- ここから後ろは body 扱いになる -->
</head>
<body>
<p>当店の商品をご紹介します。</p>
</body>
<h1> は <head> に置ける要素ではないため、ブラウザーはそこで <head> が終わったとみなし、<h1> 以降を <body> に入れて解析を続けます。その結果、後ろに書いた <title> まで <body> の中に移動してしまい、タブにタイトルが表示されなくなります。「<title> を書いたのに反映されない」というときは、その前に本文用の要素が混ざっていないかを確認してください。実際にどう解釈されたかは、開発者ツールの Elements パネルで組み立てられた DOM を見れば一目で分かります。
日本語が文字化けするとき
ブラウザーに「?????」や「ãã‚“ã«ã¡ã¯」のような読めない文字が並ぶのが文字化けです。原因はいくつかありますが、いずれも「ファイルが実際に保存されている文字コード」と「ブラウザーが解釈に使った文字コード」が食い違っていることに帰着します。
charset の指定を書き忘れている
いちばん多い原因です。<meta charset="UTF-8"> が無いと、ブラウザーは文字コードを推測するしかありません。推測が外れると化けます。まずはこの 1 行が <head> の先頭にあるかを確認してください。
書いてあるのに化ける場合は、位置を疑います。前述のとおり宣言は先頭 1024 バイト以内に収まっている必要があるので、<head> の後ろのほうに書かれていると効かないことがあります。また、<head> より前、たとえばテンプレートの都合で本文の一部が先に出力されているような場合も、宣言が読まれる位置が後ろにずれます。
ファイル自体が UTF-8 で保存されていない
<meta charset="UTF-8"> は「このファイルは UTF-8 です」という宣言であって、変換を行うものではありません。ファイルが Shift_JIS で保存されているのに UTF-8 と宣言していれば、当然ながら化けます。
エディタの下部などに表示されている文字コード(VS Code なら右下のステータスバー)を確認し、UTF-8 になっていなければ「エンコード付きで保存」から UTF-8 に変換して保存し直してください。古い案件のファイルを引き継いだときや、Windows のメモ帳で編集したときに起こりがちです。
サーバーが別の文字コードを指定している
ローカルでファイルを開くと正常なのに、サーバーにアップロードすると化ける、というケースがこれです。HTTP のレスポンスヘッダーに Content-Type: text/html; charset=Shift_JIS のような指定が含まれていると、HTML 内の <meta charset> よりもそちらが優先されます。文書内の宣言はあくまで、HTTP ヘッダーに指定が無いときのための情報です。
開発者ツールの Network パネルでそのページのリクエストを選び、レスポンスヘッダーの Content-Type を確認してください。意図しない文字コードが返っていれば、サーバー側の設定(Apache なら AddDefaultCharset など)を直す必要があります。
省略できるタグと、それでも書くべき理由
ここまで <html>・<head>・<body> は必ず書くものとして説明してきましたが、実は HTML の仕様では、この 3 つの開始タグと終了タグは条件を満たせば省略できると定められています。次のコードは、文法的に正しい HTML です。
<!DOCTYPE html>
<meta charset="UTF-8">
<title>タグを省略したページ</title>
<h1>見出し</h1>
<p>本文です。</p>
省略できるのはあくまでタグ(ソースコード上の記述)であって、要素そのものが無くなるわけではありません。ブラウザーは HTML を解析する過程で <html>・<head>・<body> を自動的に補い、DOM 上にはこれらの要素がきちんと存在します。上のコードを開発者ツールで見ると、次のような構造が組み立てられています。
<html>
<head>
<meta charset="UTF-8">
<title>タグを省略したページ</title>
</head>
<body>
<h1>見出し</h1>
<p>本文です。</p>
</body>
</html>
省略が許されているのは、この補完のルールが仕様で明確に決まっているからです。とはいえ、省略できる条件は要素ごとに細かく定められており、たとえば <html> の開始タグは中身の最初がコメントのときには省略できませんし、<body> の開始タグは中身が空白文字や <meta>・<link>・<script>・<style> から始まるときには省略できません。
実務では、これらのタグは省略せずに書くべきです。理由はいくつかあります。
head と body の境界が意図とずれる
いちばん実害が出やすいのがこれです。<body> の開始タグを省略している状態で、本文の冒頭に <script> や <style> を書くと、その要素は <head> の中に入ります。「<body> の先頭に置いたつもりのスクリプト」が実は <head> にあった、という食い違いが生まれ、document.body がまだ null でエラーになる、といったトラブルにつながります。タグを書いておけば、境界は書いたとおりに決まります。
属性を付けられない
<html> には lang を、<body> にはページ種別ごとのクラスを付けたい場面が必ず出てきます。開始タグを書いていなければ、属性を指定する場所そのものがありません。とくに lang はアクセシビリティに直結するので、<html> の開始タグは事実上必須と考えてよいでしょう。
読み手とツールに構造が伝わらない
タグが書かれていれば、ファイルを開いた瞬間に「ここまでが <head>、ここからが <body>」と目で追えます。エディタの折りたたみやインデントも構造どおりに効きます。また、テンプレートエンジンやビルドツールの中には、</head> や </body> の直前にタグを差し込む前提で動くものがあり、その文字列が無いと処理が働きません。省略して得られる数十バイトの節約より、失うもののほうがはるかに大きいのが実情です。
まとめ
HTML ファイルは、先頭の <!DOCTYPE html>、全体を包む <html>、そしてその中の <head> と <body> という骨組みでできています。<!DOCTYPE html> はタグではなく文書型宣言で、終了タグを持たず、ファイルの先頭に置きます。HTML5 で長い DTD の指定が不要になり 1 行になりましたが、役割は残っていて、これが無いとブラウザーは互換モードに切り替わり、width に padding と border が含まれるなど CSS の解釈が変わってしまいます。いまどちらのモードかは document.compatMode で確認できます。<html> はルート要素で、lang 属性に文書の言語を指定します。<head> には画面に表示されない情報を置き、<title> は省略できない必須要素、文字コードの宣言は先頭 1024 バイト以内という制約があるため <head> の 1 行目に書くのが確実です。<body> には画面に表示される内容を書き、1 文書に 1 つだけ置きます。どちらに書くか迷ったら「ページ本文として表示されるかどうか」で判断してください。<html>・<head>・<body> のタグは条件を満たせば省略できる仕様ですが、境界が意図とずれたり属性を付けられなくなったりするため、実務では省略せずに書きましょう。