티스토리 스킨을 직접 만들며 깨진 것들 — 증상만 봐서는 원인을 못 찾는 5가지
티스토리 스킨을 설치형이 아니라 직접 만들어 올렸습니다. HTML과 CSS를 쓸 줄 알면 금방 되겠다고 생각했는데, 정작 시간을 잡아먹은 건 디자인이 아니라 증상만 봐서는 원인을 짐작할 수 없는 버그들이었습니다.
공통점이 하나 있습니다. 로컬 미리보기에서는 전부 멀쩡했습니다. 라이브에 올려야만 재현됐고, 그래서 “올리기 전에 다 확인했는데 왜"라는 상황이 반복됐습니다.
기억나는 것 다섯 개를 원인까지 적어둡니다.
1. 홈은 멀쩡한데 글 페이지만 무한 로딩
가장 오래 헤맨 것입니다. 홈은 정상적으로 뜨는데 글을 클릭하면 스피너가 계속 돕니다. CSS를 의심하고, 이미지 경로를 의심하고, 스크립트를 지워보기도 했습니다. 전부 아니었습니다.
원인은 댓글이었습니다.
티스토리가 주입하는 common.js는 글 페이지에서 React 댓글 위젯을 붙이려고 합니다.
그런데 붙일 자리([##_comment_group_##])가 스킨에 없으면 마운트에 실패한 채로 멈춥니다.
댓글이 안 보이는 게 아니라 페이지 전체가 멈춥니다.
<!-- 글 영역 안에 이게 없으면 글 페이지가 무한 로딩된다 -->
<div class="area-reply">
[##_comment_group_##]
</div>
진단 요령: 홈은 정상인데 글 페이지만 멈추면 거의 이것입니다. 증상이 “로딩"이라 네트워크나 이미지를 먼저 의심하게 되는데, 콘솔을 보면 답이 빨리 나옵니다.
같은 증상의 다른 원인이 하나 더 있습니다 — 발행된 글이 0개일 때도 멈춥니다. 스킨을 만들자마자 테스트하면 이 경우에 걸리는데, 스킨 문제로 착각하기 쉽습니다. 글을 최소 하나 발행하고 확인해야 합니다.
2. $ is not a function
콘솔에 $ is not a function과 Cannot read 'initTracker'가 같이 떴습니다.
jQuery가 없다는 뜻인데, 티스토리가 알아서 주입해준다고 알고 있었습니다.
원인은 s_t3 태그의 위치였습니다.
이건 단순한 마커가 아니라 티스토리가 프레임워크와 jQuery를 주입하는 영역 표시자입니다.
저는 이걸 빈 태그로 <head>에 하나 넣어뒀는데, 그러면 주입될 영역이 없습니다.
올바른 형태는 <body> 바로 뒤부터 </body> 앞까지 본문 전체를 감싸는 것입니다.
공식 문서와 레퍼런스 테마 모두 그렇게 되어 있었습니다.
그리고 여기서 파생되는 규칙이 하나 있습니다 — 스킨 자체 스크립트는 그 영역이 끝난 뒤에 둬야 합니다. 안에 두면 같은 오류가 납니다.
3. 메뉴를 누르면 “권한이 없습니다”
네비게이션과 필터 버튼을 만들면서 /tag/제철, /category/베이킹 같은 주소를
직접 적어뒀습니다. 눌러보니 **“권한이 없습니다”**가 떴습니다.
권한 문제가 아니었습니다. 그런 태그와 카테고리가 존재하지 않았을 뿐입니다. 티스토리는 없는 분류를 열려고 하면 404가 아니라 권한 오류를 냅니다. 그래서 원인이 안 보입니다.
해결은 하드코딩을 버리는 것이었습니다.
<!-- 존재하는 항목만 자동으로 렌더된다 → 죽은 링크가 원천적으로 안 생긴다 -->
<nav>[##_blog_menu_##]</nav>
<div class="pills">[##_category_list_##]</div>
이 원칙이 나중에 한 번 더 저를 구했습니다. 분류를 직접 적으면 그 분류를 지웠을 때 링크가 죽습니다. 자동 치환자는 실제로 있는 것만 뱉으므로 그런 일이 생기지 않습니다. 수동으로 맞추고 싶은 유혹이 들 때마다 이걸 떠올렸습니다.
4. 카드가 그리드를 무시하고 1열로 쌓임
홈에 글 카드를 격자로 깔았는데 한 줄에 하나씩 세로로만 쌓였습니다. CSS를 아무리 고쳐도 안 됐습니다.
원인은 CSS가 아니라 DOM 구조였습니다. 티스토리 치환자가 카드를 뱉을 때 래퍼 태그로 한 번 감싸서 나옵니다. 그래서 그리드 컨테이너 입장에서는 자식이 하나뿐이었습니다. 격자가 될 수가 없습니다.
/* 래퍼를 레이아웃에서 투명하게 만들어 자식이 그리드 항목이 되게 한다 */
.wrapper { display: contents; }
display: contents는 그 요소의 박스를 없애고 자식을 부모의 직접 항목처럼 취급합니다.
치환자가 만드는 래퍼를 걷어낼 수 없을 때 쓸 수 있는 거의 유일한 방법이었습니다.
5. 주석에 적은 태그 이름이 실제로 동작해버림
이게 가장 이상했던 것입니다. 예방 차원에서 알아둔 것이라 실제로 터지진 않았지만, 터졌다면 원인을 절대 못 찾았을 것 같습니다.
티스토리의 치환 엔진은 HTML 파서가 아니라 문자열 치환기입니다. 그래서 HTML 주석 안에 적은 태그 이름도 진짜 태그로 봅니다.
<!-- 이 영역은 s_t3 종료 태그 뒤에 와야 한다 -->
주석에 설명하려고 종료 태그를 그대로 적으면, 엔진이 거기서 영역을 끊습니다. 그러면 그 아래 전체가 티스토리 스크립트 없이 렌더됩니다 — 2번과 똑같은 증상인데, 소스를 봐도 주석일 뿐이라 눈에 안 들어옵니다.
주석에는 태그 문자열을 쓰지 않고 말로 풀어 적기로 했습니다.
같은 함정을 최근에 한 번 더 밟았습니다. 스킨을 정리하면서 왜 지웠는지를 HTML 주석으로 꼼꼼히 남겼는데, 올리고 나서 보니 그 주석이 라이브 소스에 그대로 나와 있었습니다. 4KB쯤 됐습니다. 당연한 일인데 작업할 때는 생각이 안 났습니다. 운영 메모는 소스가 아니라 저장소 문서에 씁니다.
정리 — 다섯 개의 공통점
| 증상 | 실제 원인 | 짐작 가능했나 |
|---|---|---|
| 글 페이지만 무한 로딩 | 댓글 마운트 자리 부재 | ❌ |
$ is not a function | 영역 태그를 head에 둠 | ❌ |
| “권한이 없습니다” | 없는 분류를 하드코딩 | ❌ |
| 카드가 1열로 쌓임 | 치환자가 래퍼를 씌움 | ❌ |
| 영역이 중간에 끊김 | 주석 안의 태그 문자열 | ❌ |
전부 증상에서 원인으로 가는 길이 직관적이지 않습니다. 그리고 다섯 개 모두 로컬 미리보기에서는 재현되지 않았습니다. 티스토리가 실제로 주입하는 것들이 라이브에만 있기 때문입니다.
그래서 결국 이렇게 정리했습니다.
- 추측으로 고치지 않는다. 공식 문서와 레퍼런스 테마를 먼저 대조합니다. 다섯 개 중 네 개가 “그렇게 쓰는 게 아니었다"였습니다.
- 라이브에 올린 뒤 콘솔을 본다. 로컬에서 멀쩡한 것이 증명이 되지 않습니다.
- 막히면 콘솔 캡처가 가장 빠른 단서다. 증상 설명보다 오류 문자열 하나가 낫습니다.
디자인을 고민한 시간보다 이 다섯 개에 쓴 시간이 길었습니다. 스킨을 직접 만들 생각이라면, HTML/CSS 실력이 아니라 이 플랫폼이 무엇을 주입하는지가 관건이라는 것을 미리 알아두면 좋겠습니다.