The charset meta tag: 26 of 27 homepages declare one, and two declare it too late

The charset meta tag is on 26 of 27 homepages, all declaring UTF-8. Two sit past the 1,024-byte limit the HTML standard sets — shopify.com at 1,986 and slack.com at 11,956 — and the response header is what saves both.

Crawling & Indexing6 min read2243 views
The charset meta tag: 26 of 27 homepages declare one, and two declare it too late

FIELD TEST · 2026-08-26 · 27 homepages · single crawl · raw bytes only

Sample and method: the same 30-site panel in use since 2026-08-15, each homepage fetched once on 2026-08-26 with a browser user agent. We recorded the byte offset of the character encoding declaration in the decoded response body, the value it declares, and whether the HTTP header carried a charset too.

Twenty-six of 27 homepages ship a charset meta tag. All 26 declare UTF-8, three of them in capitals. Two put the declaration outside the window the HTML standard allows: shopify.com at byte 1,986 and slack.com at byte 11,956, against a limit of 1,024. Neither is actually broken, and the reason why is the useful part.

How we measured it

One HTTP request per site, no rendering, no retries. Inclusion was fixed before the run: final status 200, decoded body at least 10,000 bytes, body containing <body. Three of the 30 failed — stackoverflow.com and medium.com returned 403, www.reddit.com returned an 8,393-byte shell — leaving 27, the same three exclusions as previous batches.

Offsets were taken from the decoded bytes, not from a parsed document, because the limit in the specification is a byte limit. Content-Encoding was read case-insensitively and decompressed before measuring.

# Where does your own declaration sit?
curl -sL --compressed -A 'Mozilla/5.0' https://example.com/ \
  | head -c 4000 | grep -abo -i -m1 '<meta[^>]*charset[^>]*>'

One limitation worth stating: we did not test how any particular parser behaves when the declaration arrives late. We measured where the bytes are, and compared that against what the standard requires.

What the standard actually requires

The HTML Living Standard, read on 2026-08-26, sets a hard byte limit on the declaration:

"The element containing the character encoding declaration must be serialized completely within the first 1024 bytes of the document."

The number is not arbitrary. A parser has to know the encoding before it can decode the text, so it sniffs a prefix of the document, and 1,024 bytes is the size of that prefix. A declaration after that point arrives when the decision has already been made.

The same section requires the value itself: "If the attribute is present, its value must be an ASCII case-insensitive match for the string utf-8." So utf-8 and UTF-8 are both correct, and the three sites using capitals are not doing anything wrong.

The results, in one table

Almost everyone declares it, everyone who declares it says UTF-8, and the two failures of placement are both covered by a header. Here is the whole measurement.

MeasurementSites
Declares a charset meta tag26 of 27
Declares UTF-826 of 26
Uses http-equiv instead0
Declaration inside 1,024 bytes24
Declaration past 1,024 bytes2
HTTP header carries no charset3

The offsets cluster hard at the front. The median is byte 136, the earliest is byte 40 on bbc.com, and 21 of 26 sites are inside the first 300 bytes. The distribution has no middle: it runs 40, 44, 49, 54 and so on up to 771, then jumps to 1,986 and 11,956.

The one site with no charset meta tag is fine

news.ycombinator.com ships no declaration in the document at all. Its HTTP response header says Content-Type: text/html; charset=utf-8, and the standard treats that as sufficient — the in-document declaration is required only when the header does not carry one:

"If an HTML document does not start with a BOM, and its encoding is not explicitly given by Content-Type metadata … then the encoding must be specified using a meta element with a charset attribute."

The same rule rescues the two late declarers. Both shopify.com and slack.com send a charset in the response header, so the byte offset of their meta tag never gets to matter. Nothing in this sample is broken. That is the result, and we are not going to dress it up as a warning.

Where it would break, and who is close to it

The failure needs two things at once: a declaration past byte 1,024 and no charset in the HTTP header. Three sites send Content-Type: text/html with nothing after it — developer.mozilla.org, framer.com and wikipedia.org — and all three declare the encoding early, at bytes 172, 160 and 54. Zero sites sit in the intersection.

Header charsetMeta before 1,024Sites
YesYes21
YesNo2
YesNo meta at all1
NoYes3
NoNo0

This is a fragile kind of safe. The header is set by a server or a CDN, and the meta tag is set by a template; the two are usually owned by different people. Change one, and the site can land in the empty row without anyone editing the page.

An encoding declaration you cannot see in the first kilobyte is a declaration you are trusting your CDN to make for you.

Why the two late ones are late

shopify.com opens with an inline monitoring script before the head metadata, which pushes the declaration to byte 1,986. It also writes the attribute as charSet, the camel-cased form a React-based framework serialises — legal, since attribute names are matched case-insensitively.

slack.com is a different story. Its first 200 bytes contain a <script type="text/human"> holding a large ASCII-art comment, and the declaration does not appear until byte 11,956 — eleven times past the limit. It is the widest miss in the sample by an order of magnitude.

What to do on your own page

Four rules, and the first one is the whole job. The declaration is one line and it belongs at the top; everything else here is about not letting something else get in front of it.

  • Put the declaration in the first line of <head>, before any inline script or preload hint
  • Send charset=utf-8 in the Content-Type response header as well, so the two agree
  • Do not move an inline script above it to "start work earlier". That is how both late sites got there
  • Do not add a second declaration to fix a first one. The standard allows exactly one

The same two-places-must-agree pattern shows up elsewhere in the head. Our measurement of the html lang attribute across 27 homepages found the language declared in the document while Google says it ignores it, and the viewport meta tag on the same panel found twelve different strings doing one job. If you want to see the head your server actually sends rather than the one your framework prints, check what a crawler receives from the page.

Common questions

How did you measure this?

One request per homepage on 2026-08-26 with a browser user agent, bodies decompressed and written to disk, offsets found with a byte-level search on the saved file rather than on a parsed document. Every number here was recomputed from those files.

Does the charset meta tag still matter if everything is UTF-8?

The standard says yes, and gives a reason that has nothing to do with your visible text: "A character encoding declaration is required … even when all characters are in the ASCII range, because a character encoding is needed to process non-ASCII characters entered by the user in forms, in URLs generated by scripts, and so forth."

Is UTF-8 in capitals a mistake?

No. The value is matched case-insensitively, and three of the 26 sites write it that way. Pick one form and keep it consistent across your templates, for your own sake rather than the parser's.

What happens if the declaration is late and there is no header?

The parser decides on the encoding from the prefix it has, and may have to restart once the real declaration shows up. We did not reproduce that case, because no site in this sample is in it.

Should I use http-equiv instead?

It is still valid, and zero of 27 sites use it. The short form is shorter, which matters when the whole rule is about fitting inside the first kilobyte.

The charset meta tag: 26 of 27 homepages declare one, and two declare it too late