개발 이야기3분 읽기

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 같은 값이 라우팅과 겹칠 수 있습니다

확인 순서

  1. 슬러그 생성 함수 첫 줄에 NFC 정규화가 있는지 봅니다
  2. 소문자화 뒤에 다시 정규화하는지 봅니다. 터키어 İ가 여기서 걸립니다
  3. 저장된 슬러그에 결합 문자가 섞여 있는지 확인합니다. 길이가 눈에 보이는 글자 수보다 크면 의심합니다
  4. 같은 제목을 두 번 넣어 같은 슬러그가 나오는지 봅니다
  5. 데이터베이스 컬럼의 콜레이션을 확인합니다. 대소문자를 구분하지 않는 콜레이션이면 Foofoo가 충돌합니다

tools.onuel.dev슬러그 생성기에 제목을 넣으면 정규화와 치환을 거친 결과를 볼 수 있습니다. 나온 슬러그를 URL 인코딩에 넣으면 실제 주소에 들어갈 형태와 길이가 나오고, 도메인 쪽은 퓨니코드 변환에서 따로 확인합니다.

  • #슬러그
  • #유니코드
  • #URL