Godot 웹 익스포트 자체는 버튼 하나입니다. 문제는 그 결과물을 실제로 사람들이 열 수 있게 올리는 쪽이었습니다. 네 번 걸렸고 네 번 다 빌드는 성공했는데 결과가 틀린 종류였습니다.

1. wasm 이 25MiB 한도를 넘는다

index.wasm 이 37.7MB 입니다. Cloudflare 정적 자산은 파일당 25MiB 가 한도라 그대로는 업로드가 안 됩니다.

배포 스크립트가 gzip 해서 9.8MB 짜리 index.wasm.gz 로 올리고, 워커가 요청이 오면 풀어서 내보냅니다. 파일을 잘라 올리고 워커에서 이어 붙이는 방법도 있지만, 그러면 요청마다 37MB 를 워커 메모리에 쌓게 됩니다. DecompressionStream 은 스트리밍이라 그게 없습니다.

2. Content-Encoding 은 손으로 붙일 수 없다

처음엔 더 단순하게 가려고 했습니다. gzip 바이트를 그대로 흘려보내고 헤더만 붙이면 브라우저가 알아서 풀 테니까요.

headers.set("Content-Type", "application/wasm");
headers.set("Content-Encoding", "gzip");   // ← 안 된다

로컬에서 확인해 보니 응답에 그 헤더가 없었습니다. Workers 런타임이 수동으로 설정한 Content-Encoding 을 떼어냅니다. 그래서 브라우저는 gzip 바이트를 application/wasm 으로 읽으려다 매직 넘버에서 실패합니다.

받은 첫 4바이트 : 1F 8B 08 00   (gzip)
wasm 이어야 할 것: 00 61 73 6D

배포 전에 로컬에서 첫 4바이트를 찍어 보지 않았으면 그대로 나갔을 겁니다. 지금은 워커가 직접 풀어서 진짜 wasm 바이트를 내보냅니다.

3. immutable 캐시는 파일명이 안 바뀌면 독이다

이게 제일 오래 걸렸습니다.

한글 폰트를 고쳐서 배포했는데 계속 글자가 깨진다는 제보가 왔습니다. 서버에 올라간 파일을 직접 받아 보면 새 파일이 맞았습니다. 로컬에서 실행하면 멀쩡했습니다. 그런데 사람들 화면에서는 안 고쳐졌습니다.

원인은 제가 걸어 둔 캐시 헤더였습니다.

Cache-Control: public, max-age=31536000, immutable

immutable 은 "이 URL 의 내용은 절대 안 바뀐다" 는 약속입니다. 콘텐츠 해시가 붙은 파일명에나 쓰는 헤더인데, Godot 웹 익스포트는 이름이 index.wasm / index.pck 로 고정입니다. 한 번 받아 간 브라우저는 재배포를 몇 번을 하든 서버에 묻지도 않고 1년 동안 옛 파일을 씁니다. 서버에서 되돌릴 방법이 없습니다.

지금은 배포 스크립트가 wasm+pck 의 해시로 이름을 바꾸고 index.htmlexecutable 을 그 줄기로 갈아 끼웁니다.

파일캐시이유
index.htmlno-cache새 해시를 가리키는 유일한 통로
.wasm .pckimmutable이름이 곧 내용이라 안전하다

이렇게 하면 갇혀 있던 브라우저도 강제 새로고침 없이 빠져나옵니다. html 을 매번 새로 받고, 그 html 이 새 파일명을 가리키기 때문입니다.

파일명이 고정인 산출물에 immutable 을 걸면, 그건 캐시가 아니라 시한폭탄입니다.

4. 서브셋 폰트는 유저가 치는 글자를 모른다

웹 빌드 크기를 줄이려고 한글 폰트를 서브셋해서 씁니다. 스크립트가 소스의 문자열을 훑어서 실제로 쓰는 글자만 폰트에 넣습니다. 6MB 짜리 원본이 90KB 가 됩니다.

그러다 닉네임 입력을 붙였습니다. 그 순간 이 방식이 무너집니다.

  • 플레이어가 직접 치는 닉네임은 스크립트가 훑을 수 없습니다
  • 순위표에는 남의 닉네임까지 뜹니다

언젠가 반드시 누군가의 이름이 네모 상자로 나옵니다. 확률의 문제가 아니라 시간의 문제입니다.

현대 한글 음절 전체 11,172자를 넣었습니다. 90KB 가 2.4MB 가 되고 웹 전송이 10.2MB 에서 11.4MB 로 늘었습니다. 이미 37MB wasm 을 싣는 마당에 여기서 1.2MB 를 아낄 이유가 없습니다.

같이 걸렸던 것 두 가지도 적어 둡니다.

  • 테마는 그 노드 아래로만 내려갑니다. HUD 가 자기 노드에 폰트 테마를 붙이고 있었는데,

이름 입력 화면은 HUD 밖이라 그걸 못 받아 한글이 통째로 깨졌습니다. ASCII 만 멀쩡한 게 신호였습니다. 지금은 프로젝트 설정에 겁니다.

  • RichTextLabel 은 테마 기본 폰트를 안 탑니다. 순위표처럼 RichTextLabel 을 쓰는 곳은

add_theme_font_override 로 폰트를 직접 지정해야 합니다.

공통점

네 개 다 컴파일 에러도 런타임 에러도 아니었습니다. 로그는 깨끗했고 빌드는 성공했고 서버는 200 을 줬습니다. 화면을 직접 보거나 응답 바이트를 직접 찍어 보기 전까지는 아무 신호가 없었습니다.

그래서 지금은 배포 파이프라인에 확인 단계를 박아 뒀습니다. 응답의 첫 4바이트, index.html 이 가리키는 해시, 그 해시의 실제 크기, 옛 이름이 404 인지. 넷 다 자동으로 찍습니다.