Fix charset declaration

Properly declare character encoding to prevent text rendering issues and security vulnerabilities.
Harlan WiltonHarlan Wilton3 min read Published

A missing or late charset declaration forces browsers to guess your page's encoding. This causes garbled text, broken layouts, and security vulnerabilities.

What's happening

Character encoding tells the browser how to interpret bytes as text. Without it, browsers guess and often fail. Special characters, emojis, and non-ASCII text render as garbage like ’ instead of '.

Charset sniffing is a security hole. Attackers bypass XSS filters by crafting payloads that browsers misinterpret as different encodings. Lighthouse requires the charset declaration within the first 1024 bytes of HTML.

The browser checks three places for charset:

  1. HTTP Content-Type header with charset parameter.
  2. <meta charset="UTF-8"> in the first 1024 bytes.
  3. Byte Order Mark (BOM) at the start of the file.

If none of these exist or they appear too late, you fail this audit.

Diagnose

Check your HTML source

View page source and look for a charset declaration in the <head>:

<!-- Good: meta charset in first 1024 bytes -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">

Place <meta charset> as the first element in <head>.

Check HTTP headers

In DevTools Network tab:

  1. Select the main document request.
  2. Look at Response Headers for Content-Type.
  3. It must include charset: Content-Type: text/html; charset=UTF-8.

Or via command line:

curl -I https://your-site.com | grep -i content-type

Count bytes

If you have a long <head> before the charset, verify it's within 1024 bytes:

curl -s https://your-site.com | head -c 1024 | grep -i charset

Fix

1. Add meta charset tag

Place the charset declaration first in <head>:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Page Title</title>
  <!-- other head elements -->
</head>

Use UTF-8. It supports all languages and characters.

2. Set HTTP header (server-side)

Configure your server to send the charset in the Content-Type header. Browsers process this before parsing the HTML.

Nginx:

http {
  charset utf-8;
  charset_types text/html text/css application/javascript;
}

Apache (.htaccess):

AddDefaultCharset UTF-8

Express/Node.js:

app.use((req, res, next) => {
  res.setHeader('Content-Type', 'text/html; charset=UTF-8')
  next()
})

Cloudflare Workers / Edge Functions:

async function handleRequest() {
  return new Response(html, {
    headers: {
      'Content-Type': 'text/html; charset=UTF-8',
    },
  })
}

3. Move charset earlier in document

Move charset first if you have too much content before it.

<!-- Bad: charset buried after 1200 bytes of inline scripts -->
<head>
  <script>/* 800 bytes of tracking code */</script>
  <script>/* 600 bytes more */</script>
  <meta charset="UTF-8">
</head>

<!-- Good: charset first -->
<head>
  <meta charset="UTF-8">
  <script>/* tracking code */</script>
  <script>/* more scripts */</script>
</head>

Verify the fix

  1. View page source and confirm <meta charset="UTF-8"> appears within the first few lines of <head>.
  2. Check Network tab for Content-Type: text/html; charset=UTF-8 header.
  3. Run Lighthouse and confirm "Properly defines charset" audit passes.
  4. Test pages with special characters (emojis, accented letters) to make sure they render correctly.

Common mistakes

  • Using http-equiv instead of charset: Legacy formats like <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> are verbose. Use <meta charset="UTF-8">.
  • Placing charset after title or other meta: The charset must be early enough for the browser to read it before parsing text content. Put it first in <head>.
  • Wrong case for charset value: Use UTF-8. It is the canonical form.
  • Mixing encodings: Make sure your editor saves files as UTF-8. If your file is saved as ISO-8859-1 but you declare UTF-8, characters will break.
  • Large inline scripts before charset: The 1024-byte limit is firm. Move large inline scripts to external files or after the charset declaration.

Test your entire site

Different templates use different <head> structures. Scan your entire site to check that all pages declare charset correctly.

Scan Your Site with Unlighthouse
\n \n \n\n\n\n\n \n \n \n\n",[4294,4998,4999,5004,5012,5030,5047,5065,5073,5079,5084,5093,5112,5130,5148],{"__ignoreMap":1843},[4326,5000,5001],{"class":4391,"line":1766},[4326,5002,5003],{"class":4394},"\n",[4326,5005,5006,5008,5010],{"class":4391,"line":1756},[4326,5007,4329],{"class":4328},[4326,5009,4379],{"class":4332},[4326,5011,4409],{"class":4328},[4326,5013,5014,5016,5019,5021,5024,5026,5028],{"class":4391,"line":1828},[4326,5015,4442],{"class":4328},[4326,5017,5018],{"class":4332},"script",[4326,5020,4353],{"class":4328},[4326,5022,5023],{"class":4394},"/* 800 bytes of tracking code */",[4326,5025,4715],{"class":4328},[4326,5027,5018],{"class":4332},[4326,5029,4409],{"class":4328},[4326,5031,5032,5034,5036,5038,5041,5043,5045],{"class":4391,"line":1749},[4326,5033,4442],{"class":4328},[4326,5035,5018],{"class":4332},[4326,5037,4353],{"class":4328},[4326,5039,5040],{"class":4394},"/* 600 bytes more */",[4326,5042,4715],{"class":4328},[4326,5044,5018],{"class":4332},[4326,5046,4409],{"class":4328},[4326,5048,5049,5051,5053,5055,5057,5059,5061,5063],{"class":4391,"line":1984},[4326,5050,4442],{"class":4328},[4326,5052,4333],{"class":4332},[4326,5054,4337],{"class":4336},[4326,5056,4340],{"class":4328},[4326,5058,4344],{"class":4343},[4326,5060,4348],{"class":4347},[4326,5062,4344],{"class":4343},[4326,5064,4409],{"class":4328},[4326,5066,5067,5069,5071],{"class":4391,"line":4701},[4326,5068,4715],{"class":4328},[4326,5070,4379],{"class":4332},[4326,5072,4409],{"class":4328},[4326,5074,5075],{"class":4391,"line":4722},[4326,5076,5078],{"emptyLinePlaceholder":5077},true,"\n",[4326,5080,5081],{"class":4391,"line":4728},[4326,5082,5083],{"class":4394},"\n",[4326,5085,5087,5089,5091],{"class":4391,"line":5086},9,[4326,5088,4329],{"class":4328},[4326,5090,4379],{"class":4332},[4326,5092,4409],{"class":4328},[4326,5094,5096,5098,5100,5102,5104,5106,5108,5110],{"class":4391,"line":5095},10,[4326,5097,4442],{"class":4328},[4326,5099,4333],{"class":4332},[4326,5101,4337],{"class":4336},[4326,5103,4340],{"class":4328},[4326,5105,4344],{"class":4343},[4326,5107,4348],{"class":4347},[4326,5109,4344],{"class":4343},[4326,5111,4409],{"class":4328},[4326,5113,5115,5117,5119,5121,5124,5126,5128],{"class":4391,"line":5114},11,[4326,5116,4442],{"class":4328},[4326,5118,5018],{"class":4332},[4326,5120,4353],{"class":4328},[4326,5122,5123],{"class":4394},"/* tracking code */",[4326,5125,4715],{"class":4328},[4326,5127,5018],{"class":4332},[4326,5129,4409],{"class":4328},[4326,5131,5133,5135,5137,5139,5142,5144,5146],{"class":4391,"line":5132},12,[4326,5134,4442],{"class":4328},[4326,5136,5018],{"class":4332},[4326,5138,4353],{"class":4328},[4326,5140,5141],{"class":4394},"/* more scripts */",[4326,5143,4715],{"class":4328},[4326,5145,5018],{"class":4332},[4326,5147,4409],{"class":4328},[4326,5149,5151,5153,5155],{"class":4391,"line":5150},13,[4326,5152,4715],{"class":4328},[4326,5154,4379],{"class":4332},[4326,5156,4409],{"class":4328},[4286,5158,5160],{"id":5159},"verify-the-fix","Verify the fix",[4309,5162,5163,5193,5199,5202],{},[4312,5164,5165,5166,5184,5185,4301],{},"View page source and confirm ",[4294,5167,5168,5170,5172,5174,5176,5178,5180,5182],{"className":4323,"language":4324,"style":1843},[4326,5169,4329],{"class":4328},[4326,5171,4333],{"class":4332},[4326,5173,4337],{"class":4336},[4326,5175,4340],{"class":4328},[4326,5177,4344],{"class":4343},[4326,5179,4348],{"class":4347},[4326,5181,4344],{"class":4343},[4326,5183,4353],{"class":4328}," appears within the first few lines of ",[4294,5186,5187,5189,5191],{"className":4323,"language":4324,"style":1843},[4326,5188,4329],{"class":4328},[4326,5190,4379],{"class":4332},[4326,5192,4353],{"class":4328},[4312,5194,5195,5196,5198],{},"Check Network tab for ",[4294,5197,4501],{}," header.",[4312,5200,5201],{},"Run Lighthouse and confirm \"Properly defines charset\" audit passes.",[4312,5203,5204],{},"Test pages with special characters (emojis, accented letters) to make sure they render correctly.",[4286,5206,5208],{"id":5207},"common-mistakes","Common mistakes",[5210,5211,5212,5266,5280,5289,5295],"ul",{},[4312,5213,5214,5217,5218,5247,5248,4301],{},[4748,5215,5216],{},"Using http-equiv instead of charset",": Legacy formats like ",[4294,5219,5220,5222,5224,5227,5229,5231,5233,5235,5237,5239,5241,5243,5245],{"className":4323,"language":4324,"style":1843},[4326,5221,4329],{"class":4328},[4326,5223,4333],{"class":4332},[4326,5225,5226],{"class":4336}," http-equiv",[4326,5228,4340],{"class":4328},[4326,5230,4344],{"class":4343},[4326,5232,4317],{"class":4347},[4326,5234,4344],{"class":4343},[4326,5236,4687],{"class":4336},[4326,5238,4340],{"class":4328},[4326,5240,4344],{"class":4343},[4326,5242,4874],{"class":4347},[4326,5244,4344],{"class":4343},[4326,5246,4353],{"class":4328}," are verbose. Use ",[4294,5249,5250,5252,5254,5256,5258,5260,5262,5264],{"className":4323,"language":4324,"style":1843},[4326,5251,4329],{"class":4328},[4326,5253,4333],{"class":4332},[4326,5255,4337],{"class":4336},[4326,5257,4340],{"class":4328},[4326,5259,4344],{"class":4343},[4326,5261,4348],{"class":4347},[4326,5263,4344],{"class":4343},[4326,5265,4353],{"class":4328},[4312,5267,5268,5271,5272,4301],{},[4748,5269,5270],{},"Placing charset after title or other meta",": The charset must be early enough for the browser to read it before parsing text content. Put it first in ",[4294,5273,5274,5276,5278],{"className":4323,"language":4324,"style":1843},[4326,5275,4329],{"class":4328},[4326,5277,4379],{"class":4332},[4326,5279,4353],{"class":4328},[4312,5281,5282,5285,5286,5288],{},[4748,5283,5284],{},"Wrong case for charset value",": Use ",[4294,5287,4348],{},". It is the canonical form.",[4312,5290,5291,5294],{},[4748,5292,5293],{},"Mixing encodings",": Make sure your editor saves files as UTF-8. If your file is saved as ISO-8859-1 but you declare UTF-8, characters will break.",[4312,5296,5297,5300],{},[4748,5298,5299],{},"Large inline scripts before charset",": The 1024-byte limit is firm. Move large inline scripts to external files or after the charset declaration.",[4286,5302,5304],{"id":5303},"test-your-entire-site","Test your entire site",[4282,5306,5307,5308,5316],{},"Different templates use different ",[4294,5309,5310,5312,5314],{"className":4323,"language":4324,"style":1843},[4326,5311,4329],{"class":4328},[4326,5313,4379],{"class":4332},[4326,5315,4353],{"class":4328}," structures. Scan your entire site to check that all pages declare charset correctly.",[5318,5319],"u-button",{":trailing":5320,"icon":5321,"label":5322,"size":5323,"to":5324},"true","i-heroicons-rocket-launch","Scan Your Site with Unlighthouse","lg","/",[5326,5327,5328],"style",{},"html pre.shiki code .sx-uw, html code.shiki .sx-uw{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#89DDFF}html pre.shiki code .sV-QU, html code.shiki .sV-QU{--shiki-light:#22863A;--shiki-default:#22863A;--shiki-dark:#F07178}html pre.shiki code .sg-iE, html code.shiki .sg-iE{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#C792EA}html pre.shiki code .sbw7o, html code.shiki .sbw7o{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#89DDFF}html pre.shiki code .sJnJ8, html code.shiki .sJnJ8{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTBSN, html code.shiki .sTBSN{--shiki-light:#6A737D;--shiki-light-font-style:inherit;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sryBE, html code.shiki .sryBE{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#FFCB6B}html pre.shiki code .sJFDI, html code.shiki .sJFDI{--shiki-light:#005CC5;--shiki-default:#005CC5;--shiki-dark:#C3E88D}html pre.shiki code .sc1V3, html code.shiki .sc1V3{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#89DDFF}html pre.shiki code .sjz_z, html code.shiki .sjz_z{--shiki-light:#005CC5;--shiki-default:#005CC5;--shiki-dark:#F78C6C}html pre.shiki code .sqjlB, html code.shiki .sqjlB{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#BABED8}html pre.shiki code .s0YkB, html code.shiki .s0YkB{--shiki-light:#6F42C1;--shiki-default:#6F42C1;--shiki-dark:#82AAFF}html pre.shiki code .sgUNn, html code.shiki .sgUNn{--shiki-light:#E36209;--shiki-light-font-style:inherit;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .swqme, html code.shiki .swqme{--shiki-light:#D73A49;--shiki-default:#D73A49;--shiki-dark:#C792EA}html pre.shiki code .sqVJQ, html code.shiki .sqVJQ{--shiki-light:#24292E;--shiki-default:#24292E;--shiki-dark:#F07178}html pre.shiki code .smL2f, html code.shiki .smL2f{--shiki-light:#D73A49;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sm_uT, html code.shiki .sm_uT{--shiki-light:#032F62;--shiki-default:#032F62;--shiki-dark:#F07178}",{"title":1843,"searchDepth":1756,"depth":1756,"links":5330},[5331,5332,5337,5342,5343,5344],{"id":4288,"depth":1756,"text":4289},{"id":4363,"depth":1756,"text":4364,"children":5333},[5334,5335,5336],{"id":4368,"depth":1828,"text":4369},{"id":4481,"depth":1828,"text":4482},{"id":4540,"depth":1828,"text":4541},{"id":4591,"depth":1756,"text":4592,"children":5338},[5339,5340,5341],{"id":4595,"depth":1828,"text":4596},{"id":4740,"depth":1828,"text":4741},{"id":4989,"depth":1828,"text":4990},{"id":5159,"depth":1756,"text":5160},{"id":5207,"depth":1756,"text":5208},{"id":5303,"depth":1756,"text":5304},"Properly declare character encoding to prevent text rendering issues and security vulnerabilities.","md","i-heroicons-wrench-screwdriver",[5349,5350,5351,5352,5353],"charset","character encoding","utf-8","meta charset","content-type header",{"tags":5355},[5356,5357],"best-practices","browser-compatibility",{"title":4189},null,"3 min",[5362,5364],{"path":4168,"title":5363},"All Best Practices Issues",{"path":4194,"title":5365},"Fix HTML Doctype",{"title":4277,"description":5345},{"loc":4188,"lastmod":313},"learn-lighthouse/best-practices/2.charset","4VcCBwnmHy5HdpUPnUfZBwzf1LZSkOK_-AuB5mVAaiU",[5371,5374],{"title":4186,"path":4185,"stem":5372,"description":5373,"children":-1,"_path":4185},"learn-lighthouse/best-practices/13.redirects-http","Configure your server to redirect all HTTP traffic to HTTPS. Protect users and enable secure web features across your entire site.",{"title":4192,"path":4191,"stem":5375,"description":5376,"children":-1,"_path":4191},"learn-lighthouse/best-practices/3.deprecations","Replace deprecated browser APIs before they are removed. Covers document.domain, Event.path, keyCode, and sync XHR.",["Reactive",5378],{"$scolor-mode":5379,"$snuxt-seo-utils:routeRules":5381,"$stoasts":5382,"$snuxt-seo:breadcrumb:breadcrumb":5383,"$ssite-config":5390},{"preference":5380,"value":5380,"unknown":5077,"forced":3855},"system",{"head":-1,"seoMeta":-1},[],[5384,5388,5389],{"to":5385,"icon":5386,"label":5387,"ariaLabel":5387,"current":3855},"/learn-lighthouse","i-heroicons-academic-cap","Learn Google Lighthouse",{"to":4168,"label":4081,"ariaLabel":4081,"current":3855},{"to":4168,"label":4169,"ariaLabel":4169,"current":3855},{"_priority":5391,"description":5394,"env":5395,"name":5396,"titleSeparator":5397,"url":5398},{"env":5392,"url":5393,"name":5393,"description":5393,"titleSeparator":5393},-15,-3,"Google Lighthouse for your entire site.","production","Unlighthouse","·","https://unlighthouse.dev",["Set"],["ShallowReactive",5401],{"stats":-1,"search":-1,"navigation":-1,"learn-nav":-1,"learn-/learn-lighthouse/best-practices/charset":-1,"learn-/learn-lighthouse/best-practices/charset-surround":-1}]