URL 슬러그를 만들 때 걸리는 것들
정규화, 언어별 대소문자 규칙, 한글 인코딩 길이까지. 제목을 주소로 바꿀 때 실제로 문제가 되는 지점을 정리했습니다.
글 제목을 주소로 바꾸는 일은 소문자로 바꾸고 공백을 하이픈으로 치환하면 끝나 보입니다. 영문만 다룬다면 대체로 맞습니다. 그 밖의 경우에 걸리는 지점이 몇 군데 있습니다.
같아 보이는 두 문자열이 다르다
café는 화면에 한 가지로 보이지만 저장되는 방식이 두 가지입니다.
조합형(NFC) c a f é 코드포인트 4개 é = U+00E9
분해형(NFD) c a f e ́ 코드포인트 5개 e = U+0065, ́ = U+0301
두 문자열이 == 인가: False
NFC로 맞춘 뒤: True
macOS 파일 시스템은 분해형을 쓰고 대부분의 웹 입력은 조합형이라, 붙여 넣은 출처에 따라 어느 쪽이든 들어옵니다.
슬러그를 만들기 전에 NFC로 정규화합니다. 안 하면 같은 제목으로 서로 다른 슬러그 두 개가 생기고, 데이터베이스의 유니크 제약도 이를 잡지 못합니다.
악센트 제거
NFD로 분해하면 결합 문자가 별도 코드포인트로 떨어집니다. 그것만 걸러 내면 됩니다.
원문 : Crème Brûlée à Paris
NFD 후 결합문자(Mn) 제거 : Creme Brulee a Paris
슬러그 : creme-brulee-a-paris
다만 이 방법이 모든 언어에 맞지는 않습니다. 독일어에서 ü는 관례적으로 ue로 적고, ß는 ss입니다.
'ß'.upper() = 'SS' 한 글자가 두 글자가 된다
'Straße'.lower() = 'straße' 소문자화로는 안 바뀐다
Straße를 NFD 방식으로 처리하면 strasse가 아니라 strae가 됩니다 — ß에는 분해할 결합 문자가 없어 그대로 남거나 걸러집니다. 독일어를 다룬다면 언어별 치환 표가 따로 필요합니다.
소문자화가 언어마다 다르다
터키어의 대문자 İ(점 있는 I)를 소문자로 바꾸면 코드포인트가 늘어납니다.
'İstanbul'.lower() = 'i̇stanbul' 코드포인트 9개 (원본 8개)
i 뒤에 결합 점(U+0307)이 붙습니다. 이 상태로 슬러그를 만들면 눈에는 istanbul인데 실제로는 한 글자가 더 있는 문자열이 됩니다. 소문자화 뒤에 한 번 더 정규화하고 결합 문자를 걸러야 걸립니다.
한글 슬러그의 길이
한글을 그대로 슬러그에 쓰면 주소창에는 예쁘게 보이지만 실제 URL은 퍼센트 인코딩됩니다.
한글 → %ED%95%9C%EA%B8%80 (2자가 18자)
분해형이 섞여 들어오면 세 배가 됩니다.
NFD 한글 → %E1%84%92%E1%85%A1%E1%86%AB%E1%84%80%E1%85%B3%E1%86%AF (54자)
같은 두 글자인데 인코딩 결과가 18자와 54자로 갈립니다. 정규화를 안 하면 이런 슬러그가 섞여 저장되고, 링크가 서로 다른 문자열이 됩니다.
URL 길이 제한(실무에서는 2,000자 근처)에 부딪히는 경우는 드물지만, 슬러그를 잘라 저장한다면 바이트가 아니라 코드포인트 단위로 잘라야 합니다. 바이트로 자르면 UTF-8 시퀀스 중간이 끊깁니다.
도메인은 다른 규칙을 쓴다
경로와 쿼리스트링은 퍼센트 인코딩이지만, 도메인 이름은 퓨니코드입니다.
경로 /블로그 → /%EB%B8%94%EB%A1%9C%EA%B7%B8
도메인 한글.com → xn--bj0bj06e.com
둘을 섞으면 안 됩니다. 도메인에 퍼센트 인코딩을 쓰면 해석되지 않습니다.
나머지 실무 규칙
- 연속 하이픈을 하나로 줄이고 양끝을 자릅니다.
--가 남으면 보기에도 나쁘고 일부 시스템이 다르게 취급합니다 - 길이를 제한합니다. 60~80자 정도면 충분합니다. 제목이 길면 앞부분만 씁니다
- 충돌 처리를 정합니다. 같은 슬러그가 생기면
-2를 붙이는 방식이 흔합니다. 날짜를 앞에 붙여 피하는 방법도 있습니다 - 슬러그를 바꾸면 리다이렉트를 남깁니다. 이미 배포된 주소는 다른 곳에서 링크돼 있습니다
- 예약어를 피합니다.
new,edit,admin같은 값이 라우팅과 겹칠 수 있습니다
확인 순서
- 슬러그 생성 함수 첫 줄에 NFC 정규화가 있는지 봅니다
- 소문자화 뒤에 다시 정규화하는지 봅니다. 터키어
İ가 여기서 걸립니다 - 저장된 슬러그에 결합 문자가 섞여 있는지 확인합니다. 길이가 눈에 보이는 글자 수보다 크면 의심합니다
- 같은 제목을 두 번 넣어 같은 슬러그가 나오는지 봅니다
- 데이터베이스 컬럼의 콜레이션을 확인합니다. 대소문자를 구분하지 않는 콜레이션이면
Foo와foo가 충돌합니다
tools.onuel.dev의 슬러그 생성기에 제목을 넣으면 정규화와 치환을 거친 결과를 볼 수 있습니다. 나온 슬러그를 URL 인코딩에 넣으면 실제 주소에 들어갈 형태와 길이가 나오고, 도메인 쪽은 퓨니코드 변환에서 따로 확인합니다.
- #슬러그
- #유니코드
- #URL