규칙이 없으면 같은 사고가 반복됩니다 — 홈페이지 URL 체계를 뒤집은 날
영어 방문자가 개인정보처리방침을 누르면 한국어 사이트에 갇혔습니다. 원인은 홈만 규칙이 반대였던 것 — 루트는 영어인데 나머지 주소는 전부 한국어가 기본이었습니다. 규칙을 하나로 통일하면서 449개 파일을 옮기고, 조용히 실패하는 함정 세 개를 밟고, 그 셋을 도구가 잡도록 만든 기록
이 블로그는 한국어와 영어를 따로 씁니다. 한 글에 두 언어를 병기하지 않고 주소를 나눕니다 — 검색엔진은 URL 하나당 언어 하나를 가정하기 때문입니다.
그런데 그 주소 규칙이 이렇게 되어 있었습니다.
| 한국어 | 영어 | |
|---|---|---|
| 홈 | /ko/ | / |
| 포스트 | /posts/<슬러그>/ | /en/posts/<슬러그>/ |
| 소개·타로·태그 등 | /about/ /tarot/ /tags/ | /en/about/ /en/tarot/ /en/tags/ |
홈만 규칙이 반대입니다. 루트가 영어인데, 나머지는 전부 한국어가 기본이고 영어에 /en/이 붙습니다.
이렇게 된 이유는 있습니다. 원래 이 사이트는 한국어가 기본이었고, 글로벌 검색 유입을 늘리려고 루트를 영어로 올리는 개편을 했습니다. 그때 홈에만 적용하고 나머지를 그대로 뒀습니다. 각각은 동작하니까요.
규칙이 없으면 사고가 납니다
규칙이 애매한 상태로 몇 주가 지나자 실제로 문제가 나왔습니다.
영어로 보고 있던 방문자가 사이드바의 개인정보처리방침을 누르면 사이트가 한국어로 갈아탔습니다. 그리고 돌아올 방법이 없었습니다.
원인을 파 보니 이랬습니다. 개인정보처리방침 페이지는 본문이 이미 영문이었는데, front matter에 언어 선언이 없어서 사이트 기본값인 한국어를 물려받았습니다. 짝이 되는 영문판 주소도 없어서 그 페이지에는 언어 스위처가 아예 뜨지 않았습니다. 페이지가 한국어로 취급되니 사이드바 링크도 전부 한국어 주소로 바뀌고, 그다음부터는 계속 한국어입니다.
혼자 만드는 사이트에서 이런 건 티가 안 납니다. 저는 한국어 브라우저로 보니까요.
규칙 하나로 통일했습니다
영어는 /*, 한국어는 /ko/*. 예외 없이.
| 영어 | 한국어 | |
|---|---|---|
| 홈 | / | /ko/ |
| 포스트 | /posts/<슬러그>/ | /ko/posts/<슬러그>/ |
| 소개·타로·PLAY | /about/ /tarot/ /play/ | /ko/about/ /ko/tarot/ /ko/play/ |
| 아카이브·카테고리·태그 | /archives/ /categories/ /tags/ | /ko/archives/ /ko/categories/ /ko/tags/ |
| 개인정보처리방침 | /privacy/ | /ko/privacy/ |
예외를 딱 하나 뒀습니다. 언어 중립 주소입니다 — 브라우저에서 바로 하는 게임(/play/<게임>/), 토스 딥링크 랜딩(/toss/<앱>/), 아동 앱 안내(/kids/), 정적 파일. 게임은 게임 자체가 브라우저 언어를 따르고, 나머지는 앱과 스토어가 물고 있는 주소입니다.
바꿔야 할 곳이 449개 파일에 2,297군데였습니다. 손으로 하면 반드시 빠뜨립니다. 그래서 규칙을 스크립트로 적고, 그 스크립트가 전환을 수행하게 했습니다. 같은 파일이 지금은 규칙 문서 역할을 합니다 — 몇 달 뒤에 “왜 이렇게 되어 있지” 하고 되돌리지 않도록요.
옛 주소는 어떻게 되나
가장 신경 쓴 부분입니다. 한국어 글 주소가 2024년 12월부터 색인돼 있었습니다.
결과적으로 404는 하나도 나지 않습니다. 옛 한국어 주소 /posts/x/는 이제 같은 글의 영문판이 나오고, 한국어 브라우저로 들어오면 로케일 판정이 /ko/posts/x/로 보냅니다. 주소가 죽는 게 아니라 언어가 바뀌는 것이고, 검색 순위는 시간을 두고 새 주소로 옮겨갑니다.
스토어와 인스타는 손댈 것이 없었습니다
이게 가장 걱정한 부분이었는데, 전수 조사 결과 후속 작업이 0건이었습니다.
| 외부에 등록된 주소 | 쓰이는 곳 |
|---|---|
/privacy/ | Play 심사 필수, 전 앱 공용 |
/kids/, /kids/privacy/ | 아동 앱 정책상 필수 |
/apps.csv, /app-icons/* | 앱이 실행 중에 받아가는 목록 |
/toss/<앱>/ | 인스타 카드 QR이 인코딩한 값 |
/app-ads.txt | 광고 검증 크롤 경로 |
전부 언어 중립이라 그대로입니다. 특히 인스타 카드의 QR은 /toss/<앱>/을 담고 있어서 카드를 다시 만들 필요가 없었습니다. 캡션에는 애초에 사이트 주소를 넣지 않았습니다 — 인스타는 캡션의 링크가 클릭되지 않으니까요.
스토어에 등록된 웹사이트 필드도 루트 주소 그대로 뒀습니다. 그 필드는 광고 검증이 app-ads.txt를 찾아가는 경로여서, 언어 접두어를 붙이면 검증이 깨질 수 있습니다.
밟은 함정 세 개
전환 자체는 스크립트가 했지만, 빌드가 두 번 죽었습니다. 셋 다 조용히 실패하는 종류였습니다.
하나. 테마가 주소 접두어를 하드코딩하고 있었습니다. 한국어 태그 아카이브를 /ko/tags/로 옮겼는데, 태그 목록 페이지는 테마 원본 그대로 /tags/<한글>/로 링크했습니다. 그 자리는 이제 영문 태그의 공간이라 존재하지 않고, 링크 검사가 283건으로 빌드를 죽였습니다.
같은 문제가 카테고리에도 있었는데 이쪽은 검사에 걸리지도 않았습니다. 카테고리 이름은 영어값이라 접두어 없는 주소가 영문 페이지로 우연히 존재했습니다. 404가 아니라 조용히 다른 언어 페이지로 새는 쪽이라 태그보다 위험했습니다.
둘. 옛 영문 주소용 리다이렉트를 예약 글까지 만들었습니다. 아직 공개되지 않은 글의 주소로 링크하니 빌드가 죽습니다. 예약 글은 옛 주소에 공개된 적이 없어 보존할 것도 없습니다 — 미래 날짜는 제외하도록 규칙을 박았습니다.
셋. 이건 제 실수였습니다. 테마를 덮어쓴 파일에 설명을 달면서 HTML 주석을 front matter 위에 올렸습니다. 그러면 front matter가 파일 첫 줄이 아니게 되어 레이아웃 상속이 끊깁니다. 페이지가 사이드바도 없이, 주석이 본문에 그대로 찍힌 채 배포됐습니다. HTTP 200이고 링크도 정상이라 링크 검사는 통과합니다.
사람이 못 잡는 건 도구가 잡게
셋 다 눈으로 봐야 알거나, 눈으로 봐도 모르는 것들이었습니다. 그래서 각각에 검사를 붙였습니다.
- front matter 앞에 내용이 있으면 실패 — 일부러 깨뜨려 잡히는 것까지 확인
- 예약 글 점검: 한/영 날짜 일치, URL 규칙 준수, 공개된 글이 미래 글을 링크하는지, 홈 페이지네이션 쪽수 준비
- 탭 페이지에 제목이 없으면 실패
마지막 것도 실제로 걸렸습니다. 파일명 규칙까지 맞추려고 한국어 탭 파일에 ko- 접두어를 붙였는데, 제목을 명시하지 않은 페이지는 Jekyll이 파일명에서 제목을 만듭니다. 그래서 소개 페이지 제목이 “Ko About”으로 나갔습니다. 이것도 200이고 링크도 정상입니다. 라이브에서 메뉴를 하나씩 눌러보다 발견했습니다.
사이드바도 정리했습니다
여덟 줄이 평평하게 나열돼 있어서 “이것저것 갖다 붙인 잡화상” 느낌이 났습니다. 성격이 다른 세 가지가 같은 무게로 있었기 때문입니다 — 들어오는 문(HOME·ABOUT), 만든 것(PLAY·TAROT), 글 찾는 도구(ARCHIVES·CATEGORIES·TAGS).
묶음마다 얇은 구분선을 넣고 라벨은 넣지 않았습니다. 상용구인 개인정보처리방침은 아래 아이콘 줄로 내렸습니다. 글자 라벨을 붙였다가 ①단어가 어색하고 ②라벨의 상하 여백이 간격을 벌려서 선 하나로 바꿨습니다.
구분선 색으로도 한 번 헤맸습니다. 테마가 주는 테두리 색이 사이드바 배경과 밝기 차이가 8밖에 안 나서 사실상 보이지 않았습니다. 텍스트용 색을 낮은 불투명도로 쓰니 테마별로 값을 따로 정의하지 않고도 양쪽에서 보입니다.
남은 생각
규칙이 없는 상태는 공짜가 아닙니다. 홈만 반대로 둔 건 그때 5분을 아낀 선택이었는데, 그 뒤로 영어 방문자가 갇히는 버그, 사이드바가 언어를 갈아타는 버그, 검사 방향이 뒤집힌 도구를 만들었습니다. 오늘 규칙 하나로 통일하는 데 든 시간이 그 5분보다 훨씬 길었습니다.
조용한 실패는 사람이 못 잡습니다. 이번에 밟은 셋은 전부 HTTP 200이었습니다. 링크 검사도 통과했고, 빌드도 성공했습니다. 눈으로 보거나 아예 못 보는 것들이었습니다. 그래서 발견할 때마다 검사를 하나씩 붙였고, 붙인 검사가 진짜 잡는지 일부러 깨뜨려 확인했습니다. 검사를 안 깨뜨려 보면 그건 검사가 아니라 주석입니다.
규칙은 문서가 아니라 실행되는 곳에 둬야 합니다. 전환을 수행한 스크립트가 곧 규칙 문서이고, 검사 도구들이 그 규칙을 강제합니다. 메모에만 적어 두면 다음에 잊습니다 — 제가 이미 잊어서 이 사달이 났습니다.
앱 이야기는 Google Play와 앱인토스에, 브라우저에서 바로 해볼 수 있는 것들은 PLAY에 있습니다. 소식은 이 블로그와 인스타그램(@fadongkwon.soft) (새 창에서 열림)에서 전해드립니다.
