URL 인코딩, 공백·한글이 왜 %20으로 바뀔까

검색어에 한글이나 공백을 넣었더니 주소가 %ED%95%9C%EA%B8%80%20 처럼 알 수 없는 기호로 도배된 걸 본 분을 위한 글입니다. 결론부터 말하면, URL에는 한글·공백·일부 특수문자를 그대로 담을 수 없어서 브라우저가 정해진 규칙으로 바꿔 넣기 때문입니다.
규칙은 두 가지뿐입니다. 하나는 글자를 UTF-8 바이트로 쪼갠 뒤 각 바이트를 %XX로 적는 것, 다른 하나는 어디까지 바꿀지(전체 URL이냐 파라미터 값이냐)를 정하는 모드입니다. 이 두 가지만 알면 %20이 왜 공백인지, %EA%B0%80이 왜 '가'인지 전부 읽어낼 수 있습니다.
요약 ① 공백은
%20, 한글 '가'는%EA%B0%80— 글자를 UTF-8 바이트로 바꾼 뒤 바이트마다%와 16진수 두 자리를 붙입니다. ② 구성요소 모드(encodeURIComponent)는/ ? & =같은 구조 문자까지 바꾸고, 전체 URL 모드(encodeURI)는 그 구조 문자를 보존합니다. ③ 흔한 실수는 완성된 주소 전체를 구성요소 모드로 인코딩해 구조가 깨지는 것, 이미 인코딩된 문자열을 또 인코딩하는 것(이중 인코딩)입니다.
왜 이런 기호로 바뀌어 있을까
검색 결과나 지도 링크를 복사해 메신저에 붙였는데, 주소가 .../search?q=%ED%95%9C%EA%B8%80%20%EA%B0%92 처럼 되어 있어 무엇을 검색한 건지 눈으로 읽을 수 없는 경우가 대표적입니다.
반대 방향도 있습니다. 쿼리 파라미터에 한글을 그대로 넣어 링크를 만들었더니, 어떤 곳에서는 잘 열리고 어떤 곳에서는 링크가 잘려 열리는 경우입니다. URL 표준은 사용할 수 있는 문자를 제한하기 때문에, 한글·공백은 퍼센트 인코딩을 거쳐야 안전하게 전달됩니다.
즉 문제는 "글자가 깨졌다"가 아니라 "규칙대로 바뀐 상태를 사람이 못 읽는다"입니다. 규칙을 알면 바로 되돌려 읽을 수 있습니다.
도구 없이 직접 확인하는 방법
퍼센트 인코딩은 두 단계 규칙입니다. 도구 없이도 손으로, 혹은 브라우저에 이미 들어 있는 기능으로 확인할 수 있습니다.
1단계 — 어떤 글자를 바꾸는가. 영문 알파벳·숫자와 - _ . ~ 같은 일부 기호는 URL에 그대로 쓸 수 있어 바뀌지 않습니다. 그 밖의 공백·한글·?·& 등은 바꿔야 합니다(퍼센트 인코딩 규칙을 정한 RFC 3986 기준).
2단계 — 어떻게 바꾸는가. 바꿔야 하는 글자를 UTF-8 바이트로 만든 뒤, 바이트 하나하나를 % + 16진수 두 자리로 적습니다.
- 공백은 아스키 코드가 16진수로
20이라, 그대로%20이 됩니다. - 한글 '가'는 UTF-8로
EA B0 80세 바이트라, 세 덩어리가 이어져%EA%B0%80이 됩니다. 한글 한 글자가 보통%XX세 개로 나오는 이유입니다.
브라우저에 이미 이 기능이 들어 있습니다. 개발자 도구(대개 F12)의 콘솔에서 아래를 그대로 입력하면 같은 결과가 나옵니다.
encodeURIComponent("한글 값") // → "%ED%95%9C%EA%B8%80%20%EA%B0%92"
decodeURIComponent("%EA%B0%80") // → "가"
여기서 모드가 갈립니다. 파라미터 값 하나만 바꿀 때는 encodeURIComponent(구성요소), 주소 전체를 한 번에 바꿀 때는 encodeURI(전체 URL)를 씁니다. 둘의 차이는 아래 표에서 다룹니다.
문제는 콘솔을 열고 함수 이름을 정확히 쳐야 하고, 인코드/디코드와 두 모드를 오갈 때마다 다시 입력해야 한다는 점입니다.
저희 도구로 확인하기.
설치·회원가입 없이 브라우저 안에서 바로 인코딩·디코딩되며, 입력값은 서버로 전송되지 않습니다.

헷갈리기 쉬운 세 가지
가장 자주 어긋나는 지점은 모드 선택입니다. 같은 문자열이라도 어느 모드를 쓰느냐에 따라 / ? & = 같은 구조 문자의 결과가 달라집니다.
주소 https://site.com/search?q=한글 값&p=2를 두 모드로 바꾸면 이렇게 나옵니다.
| 문자 | 전체 URL(encodeURI) | 구성요소(encodeURIComponent) |
|---|---|---|
/ ? & = | 그대로 보존 | %2F %3F %26 %3D로 인코딩 |
| 공백 | %20 | %20 |
| 한글 | UTF-8 바이트로 인코딩 | UTF-8 바이트로 인코딩 |
- 전체 URL 모드:
https://site.com/search?q=%ED%95%9C%EA%B8%80%20%EA%B0%92&p=2— 구조는 살아 있고 한글·공백만 바뀝니다. - 구성요소 모드:
https%3A%2F%2Fsite.com...%3Fq%3D...%26p%3D2—:/?=&까지 전부 바뀌어, 주소로 다시 쓰면 구조가 깨집니다.
그래서 완성된 주소 전체를 구성요소 모드로 인코딩하는 것이 첫 번째 흔한 실수입니다. 파라미터 "값 하나"만 넣는 자리에는 구성요소 모드가 맞지만, 완성된 주소 전체에는 전체 URL 모드가 맞습니다.
두 번째는 이중 인코딩입니다. 이미 인코딩된 %EA%B0%80을 다시 인코딩하면 % 자체가 %25로 바뀌어 %25EA%25B0%2580이 됩니다. 한 번 디코드해도 원래 글자가 아니라 %EA%B0%80이라는 문자열이 그대로 나오면, 이중 인코딩을 의심할 수 있습니다.
세 번째는 디코딩 실패입니다. % 뒤에는 16진수 두 자리가 와야 하는데, %EA%B0%8처럼 자리가 모자라거나 %GG처럼 16진수가 아니면 디코딩할 수 없습니다. 이때 도구는 빨간 경고("디코딩할 수 없는 문자열입니다")를 띄우고 결과를 비워 둡니다.
정리
- URL에 담을 수 없는 공백·한글은 UTF-8 바이트마다
%+16진수 두 자리로 바뀝니다. 공백은%20, '가'는%EA%B0%80입니다. - **구성요소 모드(encodeURIComponent)**는
/ ? & =까지 바꾸고, **전체 URL 모드(encodeURI)**는 이 구조 문자를 보존합니다. - 완성된 주소 전체를 구성요소 모드로 바꾸면 구조가 깨집니다. 값 하나 = 구성요소, 주소 전체 = 전체 URL로 나눠 쓰면 됩니다.
- 이미 인코딩된 문자열을 또 인코딩하면
%가%25가 됩니다(이중 인코딩). - 디코딩은
%+ 16진수 두 자리가 맞아야 하며, 어긋나면 도구가 경고와 함께 결과를 비웁니다.
설치·회원가입 없이 브라우저 안에서 바로 인코딩·디코딩되며, 입력값은 서버로 전송되지 않습니다.
자주 묻는 질문
공백은 왜 %20이 되나요? +로 나올 때도 있던데요.
공백의 아스키 코드가 16진수로 20이라, 퍼센트 인코딩에서는 %20이 됩니다. +로 보이는 경우는 HTML 폼 전송에 쓰이는 application/x-www-form-urlencoded 방식에서 공백을 +로 적기 때문입니다. 이 도구가 쓰는 encodeURIComponent/encodeURI는 공백을 항상 %20으로 변환합니다.
한글이 왜 %EA%B0%80처럼 세 덩어리로 나오나요?
한글은 UTF-8로 인코딩하면 보통 3바이트를 차지하기 때문입니다. '가'는 EA B0 80 세 바이트이고, 이 각각을 %EA %B0 %80으로 적어 %EA%B0%80이 됩니다. 글자가 깨진 게 아니라 바이트 단위로 정확히 표현된 상태이며, 디코드 모드에 붙여 넣으면 다시 '가'로 돌아옵니다.
encodeURI와 encodeURIComponent는 뭐가 다른가요?
바꾸는 범위가 다릅니다. 전체 URL 모드(encodeURI)는 / ? & = : 같은 주소 구조 문자를 그대로 두고 한글·공백만 바꿉니다. 구성요소 모드(encodeURIComponent)는 그 구조 문자까지 전부 바꿉니다. 그래서 완성된 주소 전체에는 전체 URL 모드를, 파라미터 값 하나에는 구성요소 모드를 씁니다.
디코딩할 때 빨간 경고가 뜨는 이유는 뭔가요?
% 뒤에 오는 두 자리가 올바른 16진수가 아닐 때입니다. %EA%B0%8처럼 한 자리가 모자라거나 %GG처럼 16진수가 아닌 문자가 오면 브라우저의 디코딩 함수가 처리할 수 없어, 도구가 경고를 띄우고 결과를 비웁니다. 인코딩된 문자열의 일부만 복사했는지 확인해 보면 됩니다.
