본문으로 건너뛰기
ZEKILO Dev
인코딩

한글 URL이 %EC…로 바뀌는 이유 — UTF-8과 EUC-KR

2026.10.03 업데이트 · 4분 읽기

URL에는 ASCII 일부만 쓸 수 있어 한글은 바이트로 바꾼 뒤 %XX로 적습니다. UTF-8과 EUC-KR의 결과가 다른 이유, encodeURIComponent와 encodeURI의 차이, 이중 인코딩 문제를 설명합니다.

URL 인코딩·디코딩 바로 사용하기

한글 주소가 %EC%A0%9C…처럼 바뀌는 것은 깨진 것이 아니라 퍼센트 인코딩입니다. URL에는 ASCII 문자 일부만 쓸 수 있어서, 한글은 먼저 바이트(보통 UTF-8)로 바꾸고 각 바이트를 %와 16진수 두 자리로 적습니다. 「제」는 UTF-8에서 3바이트 EC A0 9C이므로 %EC%A0%9C가 됩니다. URL 인코딩·디코딩에서 방향을 「디코딩」으로 두고 붙여 넣으면 원래 글자로 되돌릴 수 있고, 문자 인코딩을 EUC-KR로 바꾸면 EUC-KR로 인코딩된 주소도 읽을 수 있습니다.

퍼센트 인코딩의 규칙

RFC 3986의 규칙을 정리하면 URL에 쓰는 문자는 셋으로 나뉩니다.

구분 문자 처리
예약되지 않은 문자 영문자, 숫자, - . _ ~ 그대로 씀
예약 문자 :/?#[]@ 와 !$&'()*+,;= 구분자로 쓰임. 값으로 넣으려면 인코딩
그 밖 한글 같은 비ASCII 문자, 공백, "<> 같은 일부 ASCII 문자 바이트로 바꿔 %XX로 적음

16진수는 대문자로 쓰기를 권하며, %ec와 %EC는 같은 바이트입니다.

입력 결과 (UTF-8, encodeURIComponent)
제킬로 %EC%A0%9C%ED%82%AC%EB%A1%9C
제킬로 dev&tools=1 %EC%A0%9C%ED%82%AC%EB%A1%9C%20dev%26tools%3D1

한글 한 글자는 UTF-8로 3바이트이므로 인코딩하면 9글자가 됩니다. 한글이 들어간 주소가 길어지는 이유입니다.

UTF-8과 EUC-KR은 결과가 다르다

퍼센트 인코딩은 바이트를 적는 방법일 뿐, 글자를 어떤 바이트로 바꿀지는 문자 인코딩이 정합니다. 같은 「제킬로」도 결과가 다릅니다.

문자 인코딩 바이트 퍼센트 인코딩
UTF-8 EC A0 9C ED 82 AC EB A1 9C (9개) %EC%A0%9C%ED%82%AC%EB%A1%9C
EUC-KR(CP949) C1 A6 C5 B3 B7 CE (6개) %C1%A6%C5%B3%B7%CE

RFC 3986은 새로 만드는 URI 형식에서 문자 데이터를 먼저 UTF-8로 바꾼 뒤 인코딩하라고 권합니다. 브라우저가 따르는 WHATWG URL 표준도 경로는 항상 UTF-8로 인코딩합니다. 다만 쿼리 문자열은 문서의 문자 인코딩을 따르게 되어 있어, EUC-KR로 만든 페이지의 링크와 폼에서는 %C1%A6… 같은 값이 나옵니다. EUC-KR로 운영되는 오래된 시스템과 연동할 때 파라미터를 EUC-KR로 인코딩해 달라는 요구를 만나는 이유입니다.

구분하는 요령은 다음과 같습니다.

  • 한글 한 글자가 %XX 세 개면 UTF-8, 두 개면 EUC-KR일 가능성이 높습니다.
  • EUC-KR 결과를 UTF-8로 풀면 실패합니다. JavaScript에서 decodeURIComponent('%C1%A6%C5%B3%B7%CE')는 URIError를 던집니다.
  • 반대로 UTF-8 결과를 EUC-KR로 풀면 오류가 나거나 알아볼 수 없는 글자가 나옵니다.

상대 시스템이 어떤 인코딩을 쓰는지는 추측하지 말고 연동 문서로 확인하세요.

encodeURIComponent, encodeURI, 폼 방식

방식 그대로 두는 문자 공백 쓰는 곳
encodeURIComponent 영문자, 숫자, -_.!~*'() %20 쿼리 값, 경로 조각 하나
encodeURI 위 문자와 ;/?:@&=+$,# %20 URL 전체
폼 방식 영문자, 숫자, *-._ + HTML 폼, URLSearchParams
  • URL 전체: https://dev.zekilo.com/ko/검색?q=a b는 https://dev.zekilo.com/ko/%EA%B2%80%EC%83%89?q=a%20b가 됩니다. :, /, ?, =는 구분자로 남습니다.
  • 폼 방식: a b&c=제는 a+b%26c%3D%EC%A0%9C가 됩니다. 공백이 +로 바뀝니다.
  • !'()*~는 encodeURIComponent에서 바뀌지 않습니다.

값 안에 &나 =가 들어 있는데 encodeURI를 쓰면 그 문자가 구분자로 읽혀 파라미터가 쪼개집니다. 쿼리 값 하나를 넣을 때는 encodeURIComponent나 URLSearchParams를 쓰세요.

자주 겪는 문제

  • 이중 인코딩: 이미 인코딩한 문자열을 다시 인코딩하면 %가 %25로 바뀝니다. %EC%A0%9C는 %25EC%25A0%259C가 됩니다. RFC 3986은 같은 문자열을 두 번 이상 인코딩하거나 디코딩하지 말라고 합니다.
  • 끊긴 시퀀스: 주소가 중간에 잘려 %E0%A4%A처럼 끝나면 디코딩 오류가 납니다. 메신저나 메일에서 긴 주소가 잘리지 않았는지 확인하세요.
  • +와 공백: 디코딩할 때 +를 공백으로 읽는 것은 폼 방식뿐입니다. decodeURIComponent('a+b')는 a+b를 그대로 돌려줍니다.
  • 한글 도메인: 호스트 이름은 퍼센트 인코딩이 아니라 퓨니코드로 바뀝니다. https://한글.kr/의 호스트는 xn--bj0bj06e.kr이 됩니다.

정리

  • %EC%A0%9C는 「제」의 UTF-8 바이트를 적은 것입니다. 디코딩하면 원래 글자가 나옵니다.
  • 새로 만드는 시스템은 UTF-8을 씁니다. EUC-KR은 상대 시스템이 요구할 때만 씁니다.
  • 값에는 encodeURIComponent, URL 전체에는 encodeURI를 쓰고, 한 번만 인코딩합니다.
  • 주소를 확인할 때는 URL 인코딩·디코딩을 쓰세요. 입력값은 브라우저 안에서만 처리됩니다.

이 글과 관련된 도구

출처와 기준

다른 가이드