URL 인코딩(퍼센트 인코딩) 가이드: 한글 주소가 %EC%95%88처럼 바뀌는 이유
브라우저 주소창에 한글 검색어를 넣고 링크를 복사하면 %EC%95%88%EB%85%95 같은 문자열이 나옵니다. 이것이 URL 인코딩입니다. 웹 개발, 마케팅 링크 관리, API 연동에서 자주 부딪히는 문제를 원리부터 살펴봅니다.
URL에는 쓸 수 있는 글자가 정해져 있다
URL의 문법은 RFC 3986에 정의되어 있습니다. 이 표준에 따르면 URL에 그대로 쓸 수 있는 문자는 영문 대소문자, 숫자, 그리고 하이픈(-), 마침표(.), 밑줄(_), 물결(~) 네 가지 기호뿐입니다. 이를 '비예약 문자(unreserved)'라고 부릅니다.
그 밖에 :, /, ?, #, &, = 같은 기호는 '예약 문자'로, URL 구조를 나누는 특별한 역할을 합니다. 예를 들어 ?는 쿼리 문자열의 시작을, &는 파라미터 사이의 구분을, #은 페이지 안 위치를 뜻합니다. 그래서 검색어 자체에 &나 #이 들어 있다면, 그대로 쓰면 URL의 의미가 완전히 바뀌어 버립니다.
퍼센트 인코딩의 동작 방식
URL에 그대로 쓸 수 없는 문자는 바이트 단위로 쪼갠 뒤, 각 바이트를 % 기호와 두 자리 16진수로 표현합니다. 이것이 퍼센트 인코딩입니다. 공백(바이트 0x20)은 %20, 앰퍼샌드(0x26)는 %26, 등호(0x3D)는 %3D가 됩니다.
한글은 먼저 UTF-8로 바이트를 만든 뒤 같은 규칙을 적용합니다. '안'은 UTF-8로 EC 95 88 세 바이트이므로 %EC%95%88이 됩니다. 한글 한 글자가 URL에서 9글자로 늘어나는 이유입니다.
안녕 하세요? → %EC%95%88%EB%85%95%20%ED%95%98%EC%84%B8%EC%9A%94%3F
a b&c=d → a%20b%26c%3Dd
디코딩은 반대 과정입니다. %EC%95%88을 만나면 세 바이트 EC 95 88로 되돌린 뒤 UTF-8로 해석해 '안'을 얻습니다. 만약 서버가 이 바이트를 EUC-KR로 해석하면 글자가 깨지는데, 오래된 한국어 웹사이트에서 검색어가 깨지는 문제의 상당수가 여기서 비롯됩니다.
공백은 %20일까, +일까
URL 인코딩에서 가장 헷갈리는 부분이 공백입니다. RFC 3986 기준으로 공백은 %20입니다. 그런데 HTML 폼이 데이터를 전송할 때 쓰는 application/x-www-form-urlencoded 형식은 쿼리 문자열의 공백을 +로 바꿉니다. 그래서 같은 'a b'가 경로에서는 a%20b, 폼 쿼리에서는 a+b로 표현될 수 있습니다.
문제는 진짜 + 기호를 보내고 싶을 때입니다. 전화번호 +82나 수식 1+1을 쿼리에 그대로 넣으면 서버는 이를 공백으로 읽을 수 있습니다. 이런 값은 반드시 %2B로 인코딩해야 합니다. 프로그래밍 언어마다 함수가 다른 규칙을 쓰므로(예: 자바스크립트의 encodeURIComponent는 %20, 일부 서버 라이브러리는 +), 주고받는 양쪽이 같은 규칙을 쓰는지 확인하는 것이 중요합니다.
전체 URL과 구성 요소는 다르게 인코딩한다
URL 전체를 한 번에 인코딩하면 안 됩니다. https://example.com/search?q=... 전체를 인코딩하면 :와 /까지 %3A, %2F로 바뀌어 더는 주소로 동작하지 않습니다. 인코딩은 파라미터 값처럼 '데이터에 해당하는 부분'에만 적용해야 합니다.
자바스크립트에는 이 차이를 반영한 두 함수가 있습니다. encodeURI는 URL 구조 문자를 남겨 두고 나머지만 인코딩하며, encodeURIComponent는 /, ?, &, =까지 모두 인코딩합니다. 쿼리 파라미터 값에는 항상 후자를 쓰는 것이 안전합니다.
이중 인코딩: %2520을 보면 의심하라
이미 인코딩된 문자열을 한 번 더 인코딩하면 %가 %25로 바뀝니다. 그래서 공백이 %20이 아니라 %2520으로 보인다면 이중 인코딩이 일어난 것입니다. 리다이렉트를 여러 번 거치는 광고 추적 링크, 다른 URL을 파라미터로 품은 로그인 return URL에서 자주 발생합니다.
반대로 디코딩을 두 번 하면 원래 데이터에 있던 %25가 %로 풀려 엉뚱한 값이 됩니다. 원칙은 간단합니다. 인코딩은 값을 URL에 넣는 마지막 순간에 한 번, 디코딩은 값을 꺼내는 처음 순간에 한 번만 합니다.
실무 체크리스트
AIBIZAP의 URL 인코더/디코더로 값을 직접 넣어 보면 어떤 글자가 어떻게 바뀌는지 바로 확인할 수 있습니다.
- UTM 파라미터 등 마케팅 링크를 만들 때 캠페인 이름에 한글이나 공백이 있으면 반드시 인코딩합니다.
- API를 호출할 때 검색어, 이메일 주소(@ 포함), 날짜(: 포함) 같은 값은 encodeURIComponent 수준으로 인코딩합니다.
- 링크가 동작하지 않으면 %25가 보이는지 확인해 이중 인코딩 여부를 점검합니다.
- 서버 로그에서 한글이 깨져 보이면 디코딩 후 UTF-8로 해석했는지 확인합니다.
- Base64 결과를 URL에 넣어야 한다면 Base64URL 변형을 쓰거나 결과를 다시 퍼센트 인코딩합니다.