[복리 계산기 제작기 번외편-1] 멀쩡한 코드가 서버에만 올리면 한글이 깨지는 이유
1. 분명 로컬에서는 멀쩡했는데
지난 편에서 만든 아이 용돈 복리 계산기를 실제 서버(heung-in.com/tools/allowance/)에 올렸습니다. 그런데 로컬 PC에서 index.html을 더블클릭해서 열었을 땐 한글이 완벽하게 나오던 게, 똑같은 파일을 서버에 올려서 브라우저 주소창에 도메인을 치고 들어가니 이렇게 나왔습니다.
◆곬쓪◆ 샏쓙(◆곬톡 3%, 슻겷뎜ETF 6%...
제목도, 버튼 글자도, 결과 카드도 전부 물음표 마름모(�)와 이상한 한자 비슷한 글자로 뒤섞여 있었습니다. 코드 자체는 하나도 안 건드렸는데 말이죠. 이번 편은 이 문제를 어떻게 추적하고 해결했는지, 시행착오를 그대로 기록합니다. 비슷한 문제를 겪는 분들께 도움이 되길 바라며 씁니다.
2. 첫 번째 가설: 서버가 잘못된 헤더를 보내는 것 아닐까?
가장 먼저 의심한 건 HTTP 응답 헤더였습니다. 브라우저가 인코딩을 판단하는 순서는 이렇습니다.
1순위: HTTP 응답 헤더의 charset > 2순위: HTML 문서 안의
<meta charset>태그
즉 서버가 Content-Type: text/html; charset=EUC-KR 같은 잘못된 헤더를 응답에 붙여서 보내고 있다면, 파일 안에 아무리 <meta charset="UTF-8">을 정확히 넣어도 브라우저는 그걸 무시하고 헤더를 따릅니다. 실제로 서버를 쓰고 있는 Apache 기본 설정 중에는 이런 식으로 charset을 강제하는 옵션이 있을 수 있어서, 이게 원인이라고 판단했습니다.
로컬에서 file://로 직접 열 땐 이 HTTP 헤더 자체가 존재하지 않으니, <meta charset> 태그가 그대로 먹혀서 정상으로 보였던 거고, 서버를 거치는 순간 헤더가 우선권을 가져가면서 깨진다는 논리였습니다.
시도 1: meta 태그 순서 조정
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="UTF-8">
http-equiv 방식의 charset 선언을 <head> 맨 위에 추가해서, 문서 자체의 선언력을 최대한 끌어올리는 방법을 시도했습니다.
시도 2: PHP로 헤더를 코드에서 직접 지정
서버 설정 파일을 건드리지 않는 선에서, 애플리케이션 코드가 직접 응답 헤더를 만들도록 하는 방법도 시도했습니다.
<?php
header('Content-Type: text/html; charset=UTF-8');
?>
PHP 스크립트가 실행되는 순간 이 코드가 최종 헤더를 결정하기 때문에, 서버의 기본 charset 설정과 무관하게 이 페이지만큼은 UTF-8로 응답하게 만드는 방법입니다.
3. 결정적 단서: 예전에 만든 도구는 왜 멀쩡했을까
여기서 중요한 비교 대상이 있었습니다. 예전에 만든 주식 기울기 분석기와 3배 레버리지 음의 복리 시뮬레이터입니다. 이 두 도구는 <meta charset="UTF-8">이 딱 한 줄만 있거나, 심지어 아예 없는 경우도 있었는데도 서버에서 문제없이 잘 돌아갔습니다.
이 사실이 제 첫 번째 가설을 뒤집었습니다. 만약 Apache가 정말로 잘못된 헤더를 강제로 보내고 있었다면, 저 두 도구도 똑같이 깨졌어야 합니다. 그런데 멀쩡했다는 건, 애초에 서버 헤더 자체는 문제가 없었다는 뜻이었습니다.
그럼 진짜 원인은 무엇이었을까요? 처음 이 문제를 겪었을 때 제가 했던 작업을 되짚어봤습니다.
“다운받은 HTML 파일을 메모장으로 열어서 복사한 다음, 서버 코드에 붙여넣었다.”
4. 진짜 원인: 메모장이 화면에 보여주는 순간 글자가 오염된다
윈도우 기본 메모장은 UTF-8로 저장된 파일을 열 때, 파일 시작 부분에 BOM(Byte Order Mark)이 없으면 인코딩을 시스템 기본값(한국어 윈도우 기준 CP949)으로 잘못 추측해서 화면에 표시하는 경우가 있습니다.
이때 벌어지는 일은 단순히 “라벨이 잘못 붙은 것”이 아닙니다. 메모장이 화면에 보여주는 글자 자체가 이미 원본과 다른 문자로 바뀌어 있는 상태입니다. 그 상태에서 화면에 보이는(이미 깨진) 텍스트를 그대로 복사하면, 클립보드에는 처음부터 오염된 문자가 담깁니다.
이후 VS Code에 붙여넣고 “UTF-8로 저장”해도 소용이 없던 이유가 여기에 있었습니다. 인코딩 표시는 정확히 UTF-8이 맞지만, 이미 오염된 내용을 정확하게(UTF-8로) 저장한 것이었기 때문입니다. 깨끗한 그릇에 상한 재료를 담은 셈이죠. 그러니 meta 태그를 아무리 손봐도, PHP로 헤더를 강제해도 근본적으로 해결이 안 됐던 겁니다.
5. 최종 해결: 복사 경로 자체를 바꾸기
결국 답은 코드 수정이 아니라 작업 방식을 바꾸는 것이었습니다.
- 메모장을 통한 복사-붙여넣기를 완전히 배제
- VS Code에서 파일을 직접 열고, 그 안에서 텍스트를 복사하거나
- 아예 파일 자체를 서버로 업로드 (VS Code Remote-SSH의 파일 업로드 기능, 혹은
scp명령)
이렇게 텍스트가 중간에 “화면에 표시되는 과정”을 거치지 않고 파일의 실제 바이트 그대로 전달되게 만드니, 그동안 시도했던 어떤 코드 수정보다 확실하게 문제가 해결됐습니다.
6. 그래도 남겨둔 안전장치
원인은 클립보드 오염이었지만, 혹시 모를 다른 환경에서의 문제를 대비해 아래 head 구조는 표준으로 계속 유지하기로 했습니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>페이지 제목</title>
</head>
<body>
<!-- 도구 내용 -->
</body>
</html>
7. 이번 트러블슈팅으로 정리된 체크리스트
앞으로 서버에 독립 HTML 파일을 올릴 때 지킬 원칙을 정리했습니다.
- [ ]
<head>최상단에http-equiv+meta charset이중으로 UTF-8 선언 - [ ] 메모장으로 절대 파일을 열지 않기 — VS Code나 다른 에디터 사용
- [ ] 가능하면 텍스트 복사가 아니라 파일 자체를 업로드해서 바이트 손실 경로를 원천 차단
- [ ] 문제가 재현되면, 예전에 문제없이 배포됐던 도구와 코드 구조를 비교해서 “정말 서버 설정 문제가 맞는지” 먼저 검증하기 (성급하게 서버 설정을 의심하지 않기)
8. 마무리하며
이번 일을 겪으며 느낀 건, 문제가 생겼을 때 **가장 그럴듯해 보이는 원인(서버 설정)**보다 **가장 최근에 내가 실제로 한 행동(메모장으로 열어서 복사)**을 먼저 의심했어야 했다는 점입니다. 코드를 아무리 고쳐도 안 풀리는 문제라면, 코드 바깥의 작업 과정 자체를 되짚어보는 게 오히려 빠른 해결책이 될 수 있다는 걸 다시 한번 배웠습니다.
다음 편에서는 이 계산기에 물가상승률을 반영하는 기능을 추가하는 과정을 이어서 기록하겠습니다.