birchholt 직접 해보고 남기는 기록

모바일 방문자에게는 제 스킨이 안 보이고 있었습니다 — 그리고 같은 날 다시 재니 뒤집혔습니다

티스토리 스킨을 직접 만들어 쓰는 블로그가 둘 있습니다. 몇 주 동안 반응형 CSS를 다듬었습니다. 640px 이하에서 로고를 한 줄로 붙이고, 메뉴를 가로 스크롤로 돌리고, 카드를 2열로 접는 그런 작업입니다. 브라우저 기기 에뮬레이션으로 360px까지 내려가며 가로 오버플로가 0인 것도 확인했습니다.

그러다 아이폰 UA로 홈을 한 번 쳐봤습니다. 1초짜리 확인이었습니다.

curl -A "<iPhone UA>" -sL -o /dev/null \
  -w '%{http_code} %{url_effective} %{num_redirects}\n' https://<블로그>/

302가 났고 /m/ 로 떨어졌습니다. 돌아온 것은 제 스킨이 아니라 플랫폼 기본 모바일 페이지였습니다.

이건 「모바일에서 스킨이 조금 깨진다」가 아닙니다. 몇 주 다듬은 CSS가 모바일 독자에게 한 줄도 닿은 적이 없다는 뜻입니다.

마커를 세었더니 전부 0이었습니다

스킨에는 제가 붙인 클래스가 있습니다. topbar·card·paging 같은 것들입니다. 서버가 내려준 HTML에서 이것들을 세면 「내 스킨이 실제로 왔는가」를 한 번에 알 수 있습니다.

셋 다 0건이었습니다. 스킨 스타일시트는 깨진 게 아니라 요청 자체가 없었습니다. <body> 태그도 제가 페이지 종류를 가르는 데 쓰는 id 대신 플랫폼이 붙인 기기 속성이 들어 있었습니다.

이 시점에서 CSS를 더 손보는 일은 의미가 없어집니다. 안 닿는 파일을 아무리 고쳐도 안 닿습니다.

탈출 스크립트를 설계했다가 성립 불가를 알았습니다

가장 먼저 떠올린 대응은 폴백이었습니다. 「/m/ 에 떨어졌으면 자바스크립트로 / 로 되돌리자.」

그 페이지에는 제 코드가 한 줄도 없습니다.

스크립트를 어디에 넣을 생각이었나를 되짚어보니, 넣을 자리가 스킨뿐이었습니다. 그런데 스킨이 안 실리는 게 지금 문제입니다. 고쳐야 할 대상 안에 해결책을 두는 설계였고, 저는 그걸 몇 분 동안 진지하게 그리고 있었습니다.

여기가 이 글에서 제가 두 번째로 크게 틀린 지점입니다. 증상을 확인한 다음 원인의 범위를 재기 전에 대책부터 그렸습니다. 「내 코드가 실리는가」를 먼저 확인했으면 설계는 시작도 안 했을 겁니다.

남은 지렛대는 하나였습니다. 관리 화면 꾸미기 쪽의 모바일웹 자동 연결 스위치. 코드로 우회할 여지가 없고, 사람이 관리자로 들어가 한 번 끄는 것 말고는 방법이 없었습니다.

같은 날 다시 재니 재현되지 않았습니다

여기까지가 오전에 확정한 내용입니다. 저는 이걸 「모바일에 스킨이 아예 안 뜬다」로 정리해서 작업 메모에 🔴 표시까지 붙여 박아뒀습니다. 남은 작업 순서도 이걸 맨 앞으로 다시 짰습니다.

그리고 같은 날 늦게, 같은 명령을 다시 돌렸습니다. 302가 나지 않았습니다. 리다이렉트 0회, 커스텀 스킨. 두 블로그 모두 그랬습니다.

오늘(2026-09-09 15:13 KST) 다시 쟀습니다. 마찬가지입니다.

항목2026-09-02 이른 측정 (기록)2026-09-09 15:13 KST (실측)
리다이렉트 횟수1 (302)0
최종 URL/m//
최종 HTTP 코드200200
<body> 태그기기 속성 (web_mobile_ios)id="tt-body-index"
스킨 마커 topbar01
스킨 마커 card06
스킨 마커 paging01
스킨 스타일시트 링크없음1개
스킨 스크립트 링크없음1개
응답 바이트약 9.8 KB40,218 B

왼쪽 칸은 그날의 작업 기록에서 옮긴 값이고, 오른쪽 칸은 방금 잰 값입니다. 왼쪽을 지금 다시 잴 방법은 없습니다.

중간에 사람이 관리자로 들어가 그 스위치를 껐습니다. 그러니까 이 뒤집힘은 원인이 있는 뒤집힘입니다. 문제는 제가 그 사실을 확인하기 전에 결론을 🔴로 확정해 저장해뒀다는 것입니다. 설정 하나로 바뀌는 값에 확정 표시를 붙였습니다.

「뒤집혔다」를 「괜찮았다」로 읽을 뻔했습니다

여기서 한 번 더 틀릴 뻔했습니다. 302가 안 나니까 「모바일 문제는 없었던 일」로 넘어가려 했습니다.

같은 UA로 네 경로를 같은 분에 나란히 쟀습니다.

경로HTTP리다이렉트응답 바이트<body>스킨 CSS스킨 JStopbar
/200040,218id="tt-body-index"111
/9200070,818id="tt-body-page"111
/m/200010,806기기 속성000
/m/9200047,577기기 속성000

아래 두 줄이 아직 살아 있습니다. 자동으로 끌려가지 않을 뿐, 모바일 경로를 직접 요청하면 지금도 기본 템플릿입니다. 본문 래퍼 클래스부터 다릅니다 — 커스텀 스킨 쪽은 제 컨테이너 안에 본문이 들어오는데, /m/ 쪽은 플랫폼 기본 뷰어 클래스입니다.

그리고 /m/ 홈이 오늘 10,806 B 입니다. 그날 기록해둔 「약 9.8 KB」와 거의 같습니다. 302가 사라진 것이지 /m/ 이 사라진 게 아닙니다. 이 둘은 다른 사건입니다.

결과가 하나 따라 나옵니다. 스킨에 넣은 것은 /m/ 경로에서 통째로 사라집니다. 반대로 글 본문에 넣은 것은 두 경로 모두에서 살아남습니다. 그래서 어떤 문구든 반드시 보여야 하는 것이라면 스킨이 아니라 본문에 둡니다. 스타일도 스킨 CSS 클래스로 주면 안 됩니다 — 문구는 남고 색과 크기만 날아가서 오히려 더 나쁜 상태가 됩니다.

플랫폼은 모바일인 걸 알고 있었습니다

한 가지가 더 나왔습니다. 아이폰 UA와 데스크톱 UA로 같은 홈을 받아 diff를 떴습니다. 4바이트 차이였고, 실질 차이는 구독 버튼의 data-device 속성 하나였습니다. 모바일 요청에는 모바일 값이, 데스크톱 요청에는 PC 값이 박혀 있었습니다. (나머지 차이는 요청마다 새로 발급되는 토큰 한 줄입니다.)

기기 판별은 정상으로 돌고 있고, 그 판별 결과로 스킨을 갈아끼우지 않기로 한 것입니다. UA 감지 실패가 아니라 설정입니다.

UA를 넷으로 늘려도 같았습니다.

UA리다이렉트응답 바이트topbar
아이폰 Safari040,2181
안드로이드 Chrome040,2181
Googlebot 스마트폰040,2181
데스크톱 Chrome040,2141

세는 방법도 거칠었습니다

작은 것 하나를 정정합니다. 그날 제가 쓴 검사는 style.css 라는 문자열을 세는 것이었습니다. 그런데 커스텀 스킨 페이지에는 플랫폼 플러그인이 넣는 동명 파일이 둘 더 있습니다. 스타일시트 링크 자체는 커스텀 쪽이 15개, /m/ 쪽이 3개입니다.

결과는 같았지만(스킨 파일은 실제로 0건) 세는 방법이 결론을 우연히 맞힌 경우입니다. 오늘은 스킨 경로가 들어간 링크만 따로 셌습니다.

아직 확인하지 못한 것

정리

이 사이트의 다른 글에서도 같은 종류의 실수를 한 번 적었습니다 — 증상이 사라진 것을 기능이 살아난 증거로 쓰지 말 것. 이번 건은 그 반대편입니다. 증상이 재현되지 않는 것을 문제가 없었다는 증거로 쓰지 말 것.