2026年のWeb開発では、文字コードはUTF-8でそろえるのが普通です。それでも、日本語の文字トラブルはなくなっていません。

たとえば、次の4つはどれも「文字化けした」と言われることがあります。

こんにちは → 縺薙s縺ォ縺。縺ッ

― と — が同じように見えるのに一致しない

😀 を1文字として数えたつもりが、JavaScriptでは2になる

珍しい漢字だけ □ になる

見た目はどれも「文字がおかしい」ですが、原因は別です。

この記事では、規格名を順番に暗記するのではなく、どこで文字の扱いが食い違ったのかを追えるようになることを目標にします。

まず、文字が表示されるまでを知る

「あ」という文字を画面に表示するまでには、いくつかの段階があります。

「あ」

Unicodeでは U+3042

UTF-8では E3 81 82

ファイルや通信で保存・転送

受け取った側がUTF-8として読む

フォントが「あ」の形を描く

この流れを知っておくと、文字トラブルをかなり切り分けやすくなります。

たとえば、

  • バイト列の読み方を間違えると、読めない文字列になる
  • 別のUnicode文字が入っていると、見た目が似ていても一致しない
  • 文字列の区切り方を間違えると、絵文字などが途中で切れる
  • フォントに字形がなければ、四角で表示される

という具合です。

「文字化け」という一語でまとめるより、どの段階で問題が起きたかを見る方が早く原因へたどり着けます。

UnicodeとUTF-8は何が違うのか

ここは最初につまずきやすいところです。

Unicodeは、文字に番号を割り当てるための大きな仕組みです。この番号をコードポイントと呼びます。

文字Unicodeコードポイント
AU+0041
U+3042
U+2015
U+2026

U+20152015は年号ではなく、16進数で書かれた文字の番号です。

UTF-8は、そのUnicodeの番号をファイルや通信で扱えるバイト列に変換する方式です。

「あ」はUnicodeではU+3042ですが、UTF-8では次の3バイトになります。

E3 81 82

かなり単純化すると、

  • Unicode: どの文字なのかを決める
  • UTF-8: その文字をどんなバイト列で保存するかを決める

という関係です。

Shift_JISは2026年でも知っておく必要がある

新しいWebシステムをShift_JISで作る、という話ではありません。

現在はUTF-8を使うのが基本です。それでもShift_JISやWindows-31J(CP932)を知っておいた方がよいのは、古いデータとの境界で今も出てくるからです。

たとえば、

  • Excelや業務システムから出力されたCSV
  • 長く運用されている社内システム
  • 昔から保存されているテキストファイル
  • 既存システムとのファイル連携

では、Shift_JIS系のデータに遭遇することがあります。

JIS X 0208、EUC-JP、ISO-2022-JPといった名前もありますが、初級者が最初から細部まで覚える必要はありません。

まずは、

現代のWebはUTF-8が基本。ただし、外から入ってくる古い日本語データには別のエンコーディングが残っている

と理解しておけば十分です。

なお、Windowsで「Shift_JIS」と呼ばれているデータには、Microsoftの拡張であるWindows-31J / CP932が含まれていることがあります。厳密には完全に同じものではありません。

Webブラウザの互換性についてはWHATWG Encoding Standardで扱いが定義されています。

「縺薙s縺ォ」と表示されたら何が起きているのか

古典的な文字化けは、保存されているバイト列と、読む側が想定したエンコーディングが違うことで起きます。

「こんにちは」をUTF-8にすると、次のバイト列になります。

E3 81 93 E3 82 93 E3 81 AB E3 81 A1 E3 81 AF

これをUTF-8として読めば「こんにちは」に戻ります。

ところが、同じバイト列をShift_JIS系として読むと、

縺薙s縺ォ縺。縺ッ

のような文字列になります。

元データのバイトが壊れたとは限りません。同じバイト列を別のルールで文字へ戻しただけです。

まず元データを残す

文字化けしたファイルを開いたときは、そのまま上書き保存しない方が安全です。

誤った解釈で開いた内容を再保存すると、元のバイト列まで変わり、復旧が難しくなることがあります。

コピーを作ってから調べます。

ファイルのエンコーディングを確認する

LinuxやmacOSなら、最初の確認にfileコマンドを使えます。

file -bi sample.csv

UTF-8と推定されれば、次のような結果になります。

text/plain; charset=utf-8

ただし、自動判定は100%ではありません。短いファイルやASCIIだけの内容では、判定材料が足りない場合があります。

エディタの表示、データを出力したアプリの仕様、HTTPヘッダーなども合わせて確認します。

CP932だと分かったらUTF-8へ変換する

iconvが使える環境なら、次のように変換できます。

iconv -f CP932 -t UTF-8 input.csv > output.csv

重要なのは、当てずっぽうで何度も変換することではありません。

元データが何で保存されているかを確認してから変換するのが基本です。

Webで文字化けしたらcharsetも見る

Webページでは、ファイルがUTF-8でも、ブラウザが別の方式として読めば表示は崩れます。

HTMLでは通常、次の指定を使います。

<meta charset="utf-8">

HTTPレスポンスにも文字コードが指定されることがあります。

Content-Type: text/html; charset=utf-8

API、HTML、データベース、CSVなど、システムの境界をまたぐたびに「このデータを何として読むのか」があります。

新しいWebシステムならUTF-8で統一し、Shift_JIS系が必要な場合は外部連携の入口や出口だけで変換する方が管理しやすくなります。

U+2015は文字化けしているわけではない

次は、読めるのに問題になるケースです。

U+2015は次の文字です。

名前はHORIZONTAL BARです。

この文字は正常なUnicode文字なので、壊れているわけではありません。

ただし、よく似た文字がいくつもあります。

文字コードポイント名前
U+2015HORIZONTAL BAR
U+2014EM DASH
U+2013EN DASH
U+30FC長音記号
-U+002DHYPHEN-MINUS

見た目が近くても、プログラムから見れば別の文字です。

console.log("―" === "—");
// false

この違いは、文章を読むだけなら問題にならないこともあります。

しかし、

  • 検索
  • 文字列の完全一致
  • タグやID
  • Lint
  • データの重複判定

では結果が変わります。

波ダッシュにも似た問題がある

日本語では次の2文字も混ざりやすいです。

〜 U+301C WAVE DASH
~ U+FF5E FULLWIDTH TILDE

「1〜10」と「1~10」は人間にはほぼ同じ意味に見えますが、文字列としては別です。

円記号とバックスラッシュは表示だけでは判断しにくい

¥\も日本語環境では昔から紛らわしい文字です。

Unicodeでは別の文字ですが、日本語向けフォントや過去の文字コードの事情によって、バックスラッシュが円記号のように表示されることがあります。

\  U+005C
¥  U+00A5

Windowsのパスで円記号のように見えても、内部にはU+005Cが入っていることがあります。

見た目だけで判断せず、必要ならコードポイントを確認します。

同じ「が」に見えるのに一致しないことがある

もう少し分かりにくい問題があります。

日本語の「が」は、Unicode上で次の2通りの表現ができます。

1つは「が」という1つのコードポイントです。

もう1つは、

か + ゙

のように、「か」と結合用の濁点を組み合わせる方法です。

画面ではほとんど同じ「が」に見えます。

しかし、そのまま比較すると一致しないことがあります。

const a = "\u304C";
const b = "\u304B\u3099";

console.log(a === b);
// false

このような差をそろえる仕組みがUnicode正規化です。

console.log(a.normalize("NFC") === b.normalize("NFC"));
// true

初級者が最初に覚えるなら、まずNFCだけで十分です。

NFKCという正規化もあり、こちらは全角英数字や半角カナなど、より広い表記の違いをそろえられます。

console.log("ABC123".normalize("NFKC"));
// ABC123

便利ですが、何でもNFKCへ変換すればよいわけではありません。

人名、作品名、識別子などでは、元の表記を残したい場合があります。検索用の値だけを正規化し、表示用の元データは保持する設計もよく使われます。

2026年の文字問題では「1文字」がさらに難しい

UTF-8が普及した後に目立つようになった問題の一つが、画面で1文字に見えるものが、内部でも1文字とは限らないことです。

分かりやすいのが絵文字です。

JavaScriptで確認すると、

console.log("A".length);
// 1

console.log("😀".length);
// 2

画面では「😀」は1文字ですが、JavaScriptのString.lengthでは2になります。

これはJavaScriptの文字列がUTF-16のコード単位を基準に扱われるためです。

そのため、

text.slice(0, 1)

のように単純に途中で切ると、絵文字を壊すことがあります。

人間が見る「1文字」で分けたいならIntl.Segmenterを使う

JavaScriptにはIntl.Segmenterがあります。

const segmenter = new Intl.Segmenter("ja", {
  granularity: "grapheme",
});

const characters = [
  ...segmenter.segment("A😀が"),
].map(({ segment }) => segment);

console.log(characters);

ここで使っているgraphemeは、画面上で利用者が1文字として認識するまとまりに近い単位です。

入力欄の「10文字まで」、カーソル移動、1文字削除といった処理では、この違いが実際の不具合につながります。

Unicodeの文字境界についてはUAX #29で定義されています。

同じ文字でも字形を指定する仕組みがある

絵文字や日本語の漢字では、さらにVariation Selectorという仕組みがあります。

たとえば、


❤️

は似ていますが、後者には絵文字表示を指定するためのコードポイントが追加されています。

漢字にも、同じ基本文字に対して字形を指定するIVS(Ideographic Variation Sequence)があります。

普段のWeb開発で毎日意識するものではありませんが、人名や公的データのように字形そのものが重要なデータでは無視できません。

「見た目が似ているから同じ文字へ置換する」と、必要な情報を失う場合があります。

□ が出たら文字コードよりフォントを疑う

文字が、

のような四角になる場合は、UTF-8やShift_JISの問題ではないことがあります。

データには正しいUnicode文字が入っているものの、使用中のフォントがその文字を持っていないケースです。

この場合は、

  • その文字のコードポイントが正しいか
  • 別のフォントなら表示できるか
  • Webフォントに必要な文字が含まれているか
  • OSやブラウザのフォントフォールバックが効いているか

を確認します。

文字コードを変換しても直らないなら、表示側を疑った方がよい場合があります。

Unicodeは2026年現在も更新されている

Unicodeは昔に完成して、そのまま固定されている規格ではありません。

2026年8月30日時点の正式版はUnicode 17.0です。2025年9月9日に公開され、4,803文字が追加されました。CJK Unified Ideographs Extension Jも追加されています。

次のUnicode 18.0は2026年9月16日に正式公開される予定です。この記事を書いている時点では、まだ正式版ではありません。

ここで気をつけたいのは、Unicodeが更新されても、

  • OS
  • ブラウザ
  • プログラミング言語のランタイム
  • Unicode関連ライブラリ
  • フォント

が同じ日にすべて対応するわけではないことです。

UTF-8で正しく保存できても、古いフォントでは表示できない、古いライブラリでは文字種を正しく判定できない、といった差は起こります。

日本では外字の問題もまだ残っている

日本語では人名や地名の漢字が特に難しい分野です。

自治体や古い業務システムでは、標準の文字だけでは足りず、システム独自の外字を作って使ってきた歴史があります。

問題は、その外字を別のシステムへ渡しても、同じ文字として通じるとは限らないことです。

現在、デジタル庁は自治体システムで利用する文字を「行政事務標準文字」として標準化しています。

2026年8月18日更新の情報では、戸籍情報システムで管理されていた文字を整理し、文字情報基盤の文字と追加の約1万字を合わせて、約7万字の行政事務標準文字が定められています。

文字情報基盤に含まれない文字向けには、表示用フォントも提供されています。

これは、UnicodeとUTF-8が普及した現在でも、「日本語のすべての字形をどのシステムでも同じように扱える」という状態にはなっていないことを示す例です。

実際に文字がおかしくなったら、何から見るか

ここまでの話を、トラブル対応の順番にすると次のようになります。

読めない文字列になった

たとえばが並んでいるなら、まずエンコーディングを疑います。

元ファイル、HTTPのcharset、CSVを出力したシステムなどを確認します。

が入っている

U+FFFD REPLACEMENT CHARACTERです。

デコードできないバイト列があったときに置かれる文字なので、元データの欠損やエンコーディングの不一致を調べます。

普通に読めるのに比較できない

のような似た別文字、Unicode正規化、不可視文字を確認します。

文字数や切り取りだけがおかしい

絵文字や結合文字を含んでいないか確認します。

JavaScriptなら.lengthslice()だけで「1文字」を扱っていないかを見ます。

四角や空白になる

コードポイントが正しいなら、フォントやOSの対応を確認します。

人名だけ字形がおかしい

異体字、Variation Selector、IVS、外字の可能性があります。

新しいWebシステムではどうしておけばよいか

2026年現在、新しく作るWebシステムなら基本方針はシンプルです。

まず、HTML、JSON、ソースコード、API、DB接続などはUTF-8を基本にします。

Shift_JISやCP932が必要なら、既存システムとの入出力部分だけで変換します。

CSVなどを受け渡す場合は、

文字コード: UTF-8
BOM: なし
改行: LF

のように仕様として決めておくと、受け取る側が推測せずに済みます。

検索では全角・半角やUnicode正規化を吸収したいことがあります。その場合でも、元の入力を保存したまま、検索用の値だけを別に作る方法があります。

また、「最大20文字」のような仕様では、バイト数なのか、JavaScriptのlengthなのか、人間が見る文字数なのかを先に決めておく必要があります。

まとめ

2026年の日本語文字問題は、Shift_JISとUTF-8を間違える古典的な文字化けだけではありません。

現在のWebではUTF-8が基本になりましたが、その上で、

  • U+2015のような似た別文字
  • Unicode正規化
  • 絵文字や結合文字
  • Variation Selectorや異体字
  • 新しいUnicodeと古いフォントの差
  • 日本語固有の外字

といった問題が残っています。

調査するときに覚えておきたいのは、規格名の一覧ではありません。

読めないならバイト列とエンコーディングを見る。読めるのに一致しないならUnicodeを見る。文字数がおかしいなら文字の区切り方を見る。表示だけがおかしいならフォントを見る。

この順番で追えば、「文字化けしたから、とりあえずUTF-8へ変換する」よりも原因に近いところから調べられます。