[{"data":1,"prerenderedAt":835},["ShallowReactive",2],{"guide-html-encoding-bugs-fixes":3},{"id":4,"title":5,"body":6,"date":826,"description":827,"extension":828,"meta":829,"navigation":830,"path":831,"readingTime":282,"seo":832,"stem":833,"__hash__":834},"guides\u002Fguides\u002Fhtml-encoding-bugs-fixes.md","Common HTML Encoding Bugs and How to Fix Them",{"type":7,"value":8,"toc":813},"minimark",[9,14,18,26,30,47,53,63,69,103,110,116,119,123,141,160,296,301,305,314,319,324,442,457,461,466,475,484,557,561,566,571,576,644,655,659,668,673,678,704,708,742,746,772,776,797,801,809],[10,11,13],"h2",{"id":12},"why-encoding-bugs-happen","Why Encoding Bugs Happen",[15,16,17],"p",{},"HTML encoding bugs occur when characters are transformed incorrectly between storage, transmission, and display. The root cause is almost always a mismatch: the bytes were encoded with one character encoding but decoded with another.",[15,19,20,21,25],{},"These bugs range from cosmetic annoyances (curly quotes showing as ",[22,23,24],"code",{},"â€\"",") to security vulnerabilities (unescaped output enabling XSS). Understanding the common patterns helps you spot and fix them quickly.",[10,27,29],{"id":28},"bug-1-mojibake-the-garbled-text-problem","Bug 1: Mojibake — The Garbled Text Problem",[15,31,32,36,37,39,40,39,43,46],{},[33,34,35],"strong",{},"Symptom",": Smart quotes, em dashes, and accented characters render as gibberish like ",[22,38,24],{},", ",[22,41,42],{},"â€œ",[22,44,45],{},"Ã©",".",[15,48,49,52],{},[33,50,51],{},"Cause",": UTF-8 encoded bytes are being interpreted as Latin-1 (ISO-8859-1). UTF-8 uses multiple bytes for non-ASCII characters. When a two-byte UTF-8 sequence is read as Latin-1, each byte becomes a separate character.",[54,55,60],"pre",{"className":56,"code":58,"language":59},[57],"language-text","UTF-8 bytes:    E2 80 94  (em dash —)\nLatin-1 decode: â  €  \"   (mojibake)\n","text",[22,61,58],{"__ignoreMap":62},"",[15,64,65,68],{},[33,66,67],{},"Fix",": Ensure your document declares UTF-8 encoding:",[54,70,74],{"className":71,"code":72,"language":73,"meta":62,"style":62},"language-html shiki shiki-themes github-light github-dark","\u003Cmeta charset=\"UTF-8\">\n","html",[22,75,76],{"__ignoreMap":62},[77,78,81,85,89,93,96,100],"span",{"class":79,"line":80},"line",1,[77,82,84],{"class":83},"sVt8B","\u003C",[77,86,88],{"class":87},"s9eBZ","meta",[77,90,92],{"class":91},"sScJk"," charset",[77,94,95],{"class":83},"=",[77,97,99],{"class":98},"sZZnC","\"UTF-8\"",[77,101,102],{"class":83},">\n",[15,104,105,106,109],{},"Also verify that your server sends the correct ",[22,107,108],{},"Content-Type"," header:",[54,111,114],{"className":112,"code":113,"language":59},[57],"Content-Type: text\u002Fhtml; charset=UTF-8\n",[22,115,113],{"__ignoreMap":62},[15,117,118],{},"The HTTP header takes precedence over the meta tag. If the server declares Latin-1, the meta tag won't save you.",[10,120,122],{"id":121},"bug-2-double-escaping","Bug 2: Double Escaping",[15,124,125,127,128,131,132,134,135,131,138,46],{},[33,126,35],{},": Users see literal entity text like ",[22,129,130],{},"&amp;lt;"," instead of ",[22,133,84],{},", or ",[22,136,137],{},"&amp;",[22,139,140],{},"&",[15,142,143,145,146,148,149,151,152,154,155,148,157,46],{},[33,144,51],{},": The text was entity-encoded twice. The first pass converts ",[22,147,140],{}," to ",[22,150,137],{},". The second pass converts the ",[22,153,140],{}," in ",[22,156,137],{},[22,158,159],{},"&amp;amp;",[54,161,165],{"className":162,"code":163,"language":164,"meta":62,"style":62},"language-javascript shiki shiki-themes github-light github-dark","\u002F\u002F Double escaping\nfunction encodeTwice(text) {\n  let first = text.replace(\u002F&\u002Fg, '&amp;')\n  let second = first.replace(\u002F&\u002Fg, '&amp;')  \u002F\u002F catches the & in &amp;\n  return second\n}\nencodeTwice('Tom & Jerry')  \u002F\u002F \"Tom &amp;amp; Jerry\"\n","javascript",[22,166,167,173,192,230,265,274,280],{"__ignoreMap":62},[77,168,169],{"class":79,"line":80},[77,170,172],{"class":171},"sJ8bj","\u002F\u002F Double escaping\n",[77,174,176,180,183,186,189],{"class":79,"line":175},2,[77,177,179],{"class":178},"szBVR","function",[77,181,182],{"class":91}," encodeTwice",[77,184,185],{"class":83},"(",[77,187,59],{"class":188},"s4XuR",[77,190,191],{"class":83},") {\n",[77,193,195,198,201,203,206,209,211,214,217,219,222,224,227],{"class":79,"line":194},3,[77,196,197],{"class":178},"  let",[77,199,200],{"class":83}," first ",[77,202,95],{"class":178},[77,204,205],{"class":83}," text.",[77,207,208],{"class":91},"replace",[77,210,185],{"class":83},[77,212,213],{"class":98},"\u002F",[77,215,140],{"class":216},"sA_wV",[77,218,213],{"class":98},[77,220,221],{"class":178},"g",[77,223,39],{"class":83},[77,225,226],{"class":98},"'&amp;'",[77,228,229],{"class":83},")\n",[77,231,233,235,238,240,243,245,247,249,251,253,255,257,259,262],{"class":79,"line":232},4,[77,234,197],{"class":178},[77,236,237],{"class":83}," second ",[77,239,95],{"class":178},[77,241,242],{"class":83}," first.",[77,244,208],{"class":91},[77,246,185],{"class":83},[77,248,213],{"class":98},[77,250,140],{"class":216},[77,252,213],{"class":98},[77,254,221],{"class":178},[77,256,39],{"class":83},[77,258,226],{"class":98},[77,260,261],{"class":83},")  ",[77,263,264],{"class":171},"\u002F\u002F catches the & in &amp;\n",[77,266,268,271],{"class":79,"line":267},5,[77,269,270],{"class":178},"  return",[77,272,273],{"class":83}," second\n",[77,275,277],{"class":79,"line":276},6,[77,278,279],{"class":83},"}\n",[77,281,283,286,288,291,293],{"class":79,"line":282},7,[77,284,285],{"class":91},"encodeTwice",[77,287,185],{"class":83},[77,289,290],{"class":98},"'Tom & Jerry'",[77,292,261],{"class":83},[77,294,295],{"class":171},"\u002F\u002F \"Tom &amp;amp; Jerry\"\n",[15,297,298,300],{},[33,299,67],{},": Encode exactly once, at the output boundary. If you're using a template engine (Vue, React, Twig), it handles encoding automatically. Don't pre-encode values before passing them to templates.",[10,302,304],{"id":303},"bug-3-missing-entity-encoding-xss-risk","Bug 3: Missing Entity Encoding (XSS Risk)",[15,306,307,309,310,313],{},[33,308,35],{},": User input containing ",[22,311,312],{},"\u003Cscript>"," tags executes as code instead of displaying as text.",[15,315,316,318],{},[33,317,51],{},": User-supplied data was inserted into HTML without entity encoding.",[15,320,321,323],{},[33,322,67],{},": Always encode user output:",[54,325,327],{"className":162,"code":326,"language":164,"meta":62,"style":62},"function escapeHtml(text) {\n  const map = { '&': '&amp;', '\u003C': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&apos;' }\n  return text.replace(\u002F[&\u003C>\"']\u002Fg, c => map[c])\n}\n",[22,328,329,342,408,438],{"__ignoreMap":62},[77,330,331,333,336,338,340],{"class":79,"line":80},[77,332,179],{"class":178},[77,334,335],{"class":91}," escapeHtml",[77,337,185],{"class":83},[77,339,59],{"class":188},[77,341,191],{"class":83},[77,343,344,347,351,354,357,360,363,365,367,370,372,375,377,380,382,385,387,390,392,395,397,400,402,405],{"class":79,"line":175},[77,345,346],{"class":178},"  const",[77,348,350],{"class":349},"sj4cs"," map",[77,352,353],{"class":178}," =",[77,355,356],{"class":83}," { ",[77,358,359],{"class":98},"'&'",[77,361,362],{"class":83},": ",[77,364,226],{"class":98},[77,366,39],{"class":83},[77,368,369],{"class":98},"'\u003C'",[77,371,362],{"class":83},[77,373,374],{"class":98},"'&lt;'",[77,376,39],{"class":83},[77,378,379],{"class":98},"'>'",[77,381,362],{"class":83},[77,383,384],{"class":98},"'&gt;'",[77,386,39],{"class":83},[77,388,389],{"class":98},"'\"'",[77,391,362],{"class":83},[77,393,394],{"class":98},"'&quot;'",[77,396,39],{"class":83},[77,398,399],{"class":98},"\"'\"",[77,401,362],{"class":83},[77,403,404],{"class":98},"'&apos;'",[77,406,407],{"class":83}," }\n",[77,409,410,412,414,416,418,420,423,425,427,429,432,435],{"class":79,"line":194},[77,411,270],{"class":178},[77,413,205],{"class":83},[77,415,208],{"class":91},[77,417,185],{"class":83},[77,419,213],{"class":98},[77,421,422],{"class":349},"[&\u003C>\"']",[77,424,213],{"class":98},[77,426,221],{"class":178},[77,428,39],{"class":83},[77,430,431],{"class":188},"c",[77,433,434],{"class":178}," =>",[77,436,437],{"class":83}," map[c])\n",[77,439,440],{"class":79,"line":232},[77,441,279],{"class":83},[15,443,444,445,448,449,452,453,456],{},"Modern frameworks escape by default. Vue's ",[22,446,447],{},"{{ }}"," syntax and React's JSX auto-encode. Only ",[22,450,451],{},"v-html"," and ",[22,454,455],{},"dangerouslySetInnerHTML"," bypass this protection.",[10,458,460],{"id":459},"bug-4-charset-declaration-in-the-wrong-place","Bug 4: Charset Declaration in the Wrong Place",[15,462,463,465],{},[33,464,35],{},": Characters display correctly in some browsers but not others, or work locally but break in production.",[15,467,468,470,471,474],{},[33,469,51],{},": The ",[22,472,473],{},"\u003Cmeta charset>"," tag appears after the first 1024 bytes of the document. The browser must guess the encoding for those initial bytes, and its guess may be wrong.",[15,476,477,479,480,483],{},[33,478,67],{},": Place the charset declaration as early as possible — ideally the first child of ",[22,481,482],{},"\u003Chead>",":",[54,485,487],{"className":71,"code":486,"language":73,"meta":62,"style":62},"\u003C!DOCTYPE html>\n\u003Chtml>\n\u003Chead>\n  \u003Cmeta charset=\"UTF-8\">\n  \u003Ctitle>My Page\u003C\u002Ftitle>\n\u003C\u002Fhead>\n",[22,488,489,502,510,519,534,548],{"__ignoreMap":62},[77,490,491,494,497,500],{"class":79,"line":80},[77,492,493],{"class":83},"\u003C!",[77,495,496],{"class":87},"DOCTYPE",[77,498,499],{"class":91}," html",[77,501,102],{"class":83},[77,503,504,506,508],{"class":79,"line":175},[77,505,84],{"class":83},[77,507,73],{"class":87},[77,509,102],{"class":83},[77,511,512,514,517],{"class":79,"line":194},[77,513,84],{"class":83},[77,515,516],{"class":87},"head",[77,518,102],{"class":83},[77,520,521,524,526,528,530,532],{"class":79,"line":232},[77,522,523],{"class":83},"  \u003C",[77,525,88],{"class":87},[77,527,92],{"class":91},[77,529,95],{"class":83},[77,531,99],{"class":98},[77,533,102],{"class":83},[77,535,536,538,541,544,546],{"class":79,"line":267},[77,537,523],{"class":83},[77,539,540],{"class":87},"title",[77,542,543],{"class":83},">My Page\u003C\u002F",[77,545,540],{"class":87},[77,547,102],{"class":83},[77,549,550,553,555],{"class":79,"line":276},[77,551,552],{"class":83},"\u003C\u002F",[77,554,516],{"class":87},[77,556,102],{"class":83},[10,558,560],{"id":559},"bug-5-inconsistent-encoding-across-layers","Bug 5: Inconsistent Encoding Across Layers",[15,562,563,565],{},[33,564,35],{},": Data looks correct in the database but displays incorrectly on the page, or works in one environment but not another.",[15,567,568,570],{},[33,569,51],{},": Different parts of the stack use different encodings. The database stores Latin-1, the application processes UTF-8, and the server header declares something else.",[15,572,573,575],{},[33,574,67],{},": Audit every layer:",[577,578,579,592],"table",{},[580,581,582],"thead",{},[583,584,585,589],"tr",{},[586,587,588],"th",{},"Layer",[586,590,591],{},"Check",[593,594,595,607,617,625,633],"tbody",{},[583,596,597,601],{},[598,599,600],"td",{},"Database",[598,602,603,606],{},[22,604,605],{},"SHOW VARIABLES LIKE 'character_set%';"," (MySQL)",[583,608,609,612],{},[598,610,611],{},"Connection",[598,613,614],{},[22,615,616],{},"SET NAMES utf8mb4;",[583,618,619,622],{},[598,620,621],{},"Application",[598,623,624],{},"Source files saved as UTF-8",[583,626,627,630],{},[598,628,629],{},"Template",[598,631,632],{},"Meta charset declared",[583,634,635,638],{},[598,636,637],{},"HTTP",[598,639,640,643],{},[22,641,642],{},"Content-Type: text\u002Fhtml; charset=UTF-8"," header",[15,645,646,647,650,651,654],{},"Use ",[22,648,649],{},"utf8mb4"," (not ",[22,652,653],{},"utf8",") in MySQL to support full Unicode including 4-byte characters like emojis.",[10,656,658],{"id":657},"bug-6-bom-related-issues","Bug 6: BOM-Related Issues",[15,660,661,663,664,667],{},[33,662,35],{},": A mysterious blank space or ",[22,665,666],{},"ï»¿"," appears at the top of the page. PHP complains about \"headers already sent.\"",[15,669,670,672],{},[33,671,51],{},": The file was saved with a UTF-8 Byte Order Mark (BOM) — three invisible bytes (EF BB BF) at the start of the file.",[15,674,675,677],{},[33,676,67],{},": Save files as \"UTF-8 without BOM.\" Most editors have this option. For existing files, strip the BOM:",[54,679,683],{"className":680,"code":681,"language":682,"meta":62,"style":62},"language-bash shiki shiki-themes github-light github-dark","# Remove BOM from a file\nsed -i '1s\u002F^\\xEF\\xBB\\xBF\u002F\u002F' file.html\n","bash",[22,684,685,690],{"__ignoreMap":62},[77,686,687],{"class":79,"line":80},[77,688,689],{"class":171},"# Remove BOM from a file\n",[77,691,692,695,698,701],{"class":79,"line":175},[77,693,694],{"class":91},"sed",[77,696,697],{"class":349}," -i",[77,699,700],{"class":98}," '1s\u002F^\\xEF\\xBB\\xBF\u002F\u002F'",[77,702,703],{"class":98}," file.html\n",[10,705,707],{"id":706},"prevention-checklist","Prevention Checklist",[709,710,711,721,728,731,734,739],"ul",{},[712,713,714,715,718,719],"li",{},"Set ",[22,716,717],{},"\u003Cmeta charset=\"UTF-8\">"," as the first element in ",[22,720,482],{},[712,722,723,724,727],{},"Verify the server sends ",[22,725,726],{},"charset=UTF-8"," in the Content-Type header",[712,729,730],{},"Save all source files as UTF-8 without BOM",[712,732,733],{},"Encode user output exactly once, at the HTML boundary",[712,735,646,736,738],{},[22,737,649],{}," for MySQL databases",[712,740,741],{},"Test with non-ASCII characters: accented letters, emojis, CJK characters",[10,743,745],{"id":744},"key-takeaways","Key Takeaways",[709,747,748,751,757,760,766,769],{},[712,749,750],{},"Mojibake happens when UTF-8 bytes are decoded as Latin-1 — fix the charset declaration",[712,752,753,754,756],{},"Double escaping produces visible entity text like ",[22,755,159],{}," — encode exactly once",[712,758,759],{},"Unescaped output enables XSS — always encode user data before rendering",[712,761,762,763,765],{},"Place ",[22,764,473],{}," within the first 1024 bytes of the document",[712,767,768],{},"Audit every layer of your stack for encoding consistency",[712,770,771],{},"Save files as UTF-8 without BOM to avoid invisible byte issues",[10,773,775],{"id":774},"related-guides","Related Guides",[709,777,778,785,791],{},[712,779,780],{},[781,782,784],"a",{"href":783},"\u002Fguides\u002Fhtml-entities-guide","HTML Entities Guide",[712,786,787],{},[781,788,790],{"href":789},"\u002Fguides\u002Fhtml-special-characters-reference","HTML Special Characters Reference",[712,792,793],{},[781,794,796],{"href":795},"\u002Fguides\u002Furl-encoding-guide","URL Encoding Guide",[10,798,800],{"id":799},"try-it-yourself","Try It Yourself",[15,802,803,804,808],{},"Encode and decode HTML entities instantly with our free ",[781,805,807],{"href":806},"\u002Ftools\u002Fhtml-entity","HTML Entity Encoder\u002FDecoder",". Paste text with encoding issues and get correctly encoded output in one click.",[810,811,812],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sA_wV, html code.shiki .sA_wV{--shiki-default:#032F62;--shiki-dark:#DBEDFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":62,"searchDepth":175,"depth":175,"links":814},[815,816,817,818,819,820,821,822,823,824,825],{"id":12,"depth":175,"text":13},{"id":28,"depth":175,"text":29},{"id":121,"depth":175,"text":122},{"id":303,"depth":175,"text":304},{"id":459,"depth":175,"text":460},{"id":559,"depth":175,"text":560},{"id":657,"depth":175,"text":658},{"id":706,"depth":175,"text":707},{"id":744,"depth":175,"text":745},{"id":774,"depth":175,"text":775},{"id":799,"depth":175,"text":800},"2026-05-28","Identify and fix the most frequent HTML encoding problems — mojibake, double escaping, charset mismatches, and entity errors.","md",{"immutable":830},true,"\u002Fguides\u002Fhtml-encoding-bugs-fixes",{"title":5,"description":827},"guides\u002Fhtml-encoding-bugs-fixes","BcuKTUT3tiUvXcIpu4HyvYk-ToDZB-WvMudTqsC225M",1780401334250]