squirrelscan
GitHub

meta charset UTF-8: where it goes and what squirrel checks

A page with no charset declaration, one that is not UTF-8, a header that disagrees with the page, or a meta charset past the first 1024 bytes. squirrel checks all three sources.

What the charset declaration does

A charset declaration tells the browser how to turn bytes into characters. Get it wrong and text renders as mojibake, because the bytes for an accented character in one encoding mean something else in another.

There are three places to declare it: the Content-Type response header, a <meta charset> element, and a <meta http-equiv="Content-Type"> element. The header wins over both meta forms, and between the two meta forms the browser takes whichever comes first in the document. The meta element has to appear within the first 1,024 bytes of the document, because the browser starts guessing before then and reparses if it guessed wrong. UTF-8 is the encoding to use; the HTML Standard requires it.

What squirrel checks

The rule runs on every crawled page and reads all three sources: any <meta> with a charset attribute, a <meta http-equiv="Content-Type"> whose content names a charset, and the Content-Type response header. The page’s own declaration is the <meta charset> value, or the http-equiv one when there is no <meta charset>. When the header names a charset, that is the value the rule judges, because it is the one the browser decodes with. It emits one check named charset:

  • Skipped with the message No document available and the reason Parse error when the page did not parse.
  • Warning with the message Conflicting charset declarations: header <header value>, page <page value> and the expectation UTF-8 in both the Content-Type header and the page when one of the header and the page declares UTF-8 and the other does not. The header wins, so a page that says UTF-8 under a header that says iso-8859-1 is decoded as the latter and its accented text breaks.
  • Warning with the message Charset meta is not within the first 1024 bytes and the expectation charset meta serialized completely within the first 1024 bytes when the value is UTF-8 but the first <meta> declaring a charset does not end within the first 1,024 bytes of the HTML. This is an HTML-conformance issue even when the header also says UTF-8. Comments are skipped, as the browser skips them. Pages crawled with browser rendering are not measured, because their HTML is the rendered DOM, not the bytes the server sent.
  • Passing with the message UTF-8 charset declared early in head when the value is UTF-8 and the meta element is the first child of <head>, or the second child directly after a <title>.
  • At info status with the message UTF-8 is declared correctly; consider placing the charset meta first in head (best practice) and the expectation charset meta should be first element in head when it is UTF-8 and within the first 1,024 bytes but sits later in <head>. This is placement advice, not an encoding problem: info checks do not count toward the score and are not listed as issues in the report.
  • Passing with the message UTF-8 charset declared via http-equiv or UTF-8 charset declared via header when there is no <meta charset> element but one of the other two sources declared UTF-8.
  • Warning with the message Non-UTF-8 charset declared: <value> and the expectation UTF-8 for anything else.
  • Failing with the message No charset declaration found and the expectation <meta charset="UTF-8"> when none of the three declared anything.

The header’s charset reaches the rule however the page was fetched, plain HTTP or cloud browser rendering. A page stored by an earlier squirrel version, which recorded only the media type, and reused from the local crawl cache is read without the header’s charset until it is fetched again.

The charset and http-equiv attribute names are matched case-insensitively, so React’s charSet and httpEquiv output is recognised. The UTF-8 test accepts utf8 and utf-8 in any case, and a quoted header value such as charset="utf-8" is read without its quotes. Two different non-UTF-8 labels, such as iso-8859-1 in the header and windows-1252 in the page, are not reported as a conflict, because the labels can name the same encoding; the page gets the Non-UTF-8 warning for the header’s value. The position check allows a <title> before the charset meta, which is a common template ordering, even though the standard’s advice is to put the charset first.

How to fix it

<head>
  <meta charset="UTF-8" />
  <title>Page title</title>
</head>

Make it the first element in <head>. Sending Content-Type: text/html; charset=utf-8 from the server as well is worth doing, because a header removes the guessing entirely.

The common false positive is a site whose origin sets the charset in the header on some routes and not others. The rule falls back to the header when there is no meta element, so two pages from the same template can pass and fail. Declaring it in the markup makes the result stable.

Rule IDcore/charset
CategoryCore SEO
ScopePer-page
Severitywarning
Weight5/10

Enable / disable

Disable this rule

[rules]
disable = ["core/charset"]

Disable all Core SEO rules

[rules]
disable = ["core/*"]

Enable only this rule

[rules]
enable = ["core/charset"]

enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.

Core SEO findings ship in every audit next to the performance, security and agent experience rules. See Fix SEO issues with AI for how an agent works through a report.

References

Check your site

Run squirrel audit https://example.com and open the Core SEO section of the report. Each page reports the charset it declared and where. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.

Type to search…

↑↓ navigate↵ openesc close