개발자 도구7 분 읽기|MJ민재

한글 파라미터 넣었더니 %EC%95%88... 이게 뭐야 — URL 인코딩 5분 만에 이해하기

URL 인코딩이 뭔지, 왜 한글이 %XX로 바뀌는지, encodeURI와 encodeURIComponent 차이, 공백이 %20인지 +인지 헷갈리는 문제까지 한번에 정리했어요.

프론트엔드 개발 2년 차 때 일이에요. 검색 기능에 한글 키워드를 넣으면 API가 에러를 뱉는다는 QA 보고가 올라왔어요. 브라우저 개발자 도구 네트워크 탭을 열어보니 요청 URL에 '서울 맛집'이 그대로 박혀 있었어요. 공백도 있고 한글도 있고, 인코딩은 전혀 안 된 상태로요. 수정은 encodeURIComponent() 하나면 끝이었는데, 그걸 모르고 세 시간 동안 다른 데를 팠어요.

URL 인코딩은 한번 제대로 이해하면 다시는 막히지 않아요. 코드 한 줄이면 끝나거든요. 복잡하게 생각할 거 없어요. 지금부터 딱 필요한 것만 정리해드릴게요.

이 글에서 알 수 있는 것

  • 한글이 왜 %EC%95%88 같은 문자열로 바뀌는지, 그 원리를 알 수 있어요
  • encodeURI와 encodeURIComponent 중 언제 뭘 써야 하는지 헷갈리지 않게 정리돼 있어요
  • 이중 인코딩 함정을 포함해서, 실제로 많이 하는 실수 다섯 가지와 해결법을 알 수 있어요

URL 인코딩이 뭔가요?

URL에는 쓸 수 있는 문자가 정해져 있어요. 영문자, 숫자, 그리고 -, _, ., ~ 네 가지 특수문자만 그냥 써도 돼요. 그 외의 문자, 그러니까 한글, 공백, &, = 같은 건 퍼센트 기호와 16진수 조합으로 변환해야 브라우저랑 서버가 제대로 이해해요. 이걸 URL 인코딩 또는 퍼센트 인코딩이라고 해요.

원리는 간단해요. 문자를 UTF-8 바이트로 변환하고, 각 바이트를 %XX 형태로 써주면 돼요. 공백은 1바이트라서 %20이 되고, 한글 '가'는 UTF-8로 3바이트라서 %EA%B0%80이 돼요. 한글 한 글자에 % 세 개가 붙는 이유가 여기 있어요.

  • 공백 → %20 (HTML 폼에서는 + — 이게 왜 헷갈리는지는 아래에서 설명해요)
  • & → %26 (쿼리 스트링 구분자로 예약된 문자)
  • = → %3D (key=value 할당에 쓰이는 예약 문자)
  • / → %2F (경로 구분자로 예약된 문자)
  • ? → %3F (쿼리 스트링 시작을 나타내는 예약 문자)
  • # → %23 (프래그먼트 식별자)
  • 한글, 이모지, 일본어 등 → 바이트마다 %XX 하나씩, 글자당 3~4개

지금 이 도구를 사용해 보세요:

URL 인코더/디코더 사용해보기

encodeURI vs encodeURIComponent — 뭐가 다른 거예요?

자바스크립트에 URL 인코딩 함수가 두 개 있어요. encodeURI()랑 encodeURIComponent()예요. 이름만 보면 비슷해 보이는데, 쓰는 상황이 완전히 달라요. 이 차이를 모르면 인코딩을 했는데도 URL이 깨지는 황당한 상황을 만나요.

encodeURI()는 URL 전체를 넘길 때 써요. URL의 구조적인 문자인 /, ?, &, =, # 같은 건 건드리지 않아요. 이 문자들이 URL에서 의미를 가지고 있으니까요. encodeURIComponent()는 URL 안에 들어갈 '값' 하나를 인코딩할 때 써요. &와 = 같은 구분자도 다 인코딩해버려요. 쿼리 파라미터 값에 &가 들어가면 URL 구조 자체가 깨질 수 있으니까요.

// encodeURI — URL 전체를 넘길 때
encodeURI('https://example.com/search?q=서울 맛집&page=2')
// → 'https://example.com/search?q=%EC%84%9C%EC%9A%B8%20%EB%A7%9B%EC%A7%91&page=2'
// 한글이랑 공백은 인코딩됐고, &랑 =는 그대로예요

// encodeURIComponent — 쿼리 파라미터 '값'을 넣을 때
encodeURIComponent('서울 맛집&page=2')
// → '%EC%84%9C%EC%9A%B8%20%EB%A7%9B%EC%A7%91%26page%3D2'
// &가 %26이 됐어요. 값 안에 & 있어도 URL 구조 안 깨져요

// 실제로 URL 만들 때는 이렇게
const keyword = '서울 맛집';
const url = `https://api.example.com/search?q=${encodeURIComponent(keyword)}`;
비교 항목encodeURI()encodeURIComponent()
쓰는 상황URL 전체를 인코딩할 때쿼리 값 하나를 URL에 넣을 때
공백 인코딩예 → %20예 → %20
& 인코딩아니요 (그대로)예 → %26
= 인코딩아니요 (그대로)예 → %3D
/ 인코딩아니요 (그대로)예 → %2F
? 인코딩아니요 (그대로)예 → %3F
한글 인코딩

공백은 %20이에요, +예요?

이거 헷갈리는 사람이 많아요. 정답은 '상황에 따라 다르다'인데요, 실무에서는 단순하게 생각하면 돼요. URL 경로나 쿼리 스트링에서 공백의 표준 인코딩은 %20이에요. encodeURIComponent()도 %20을 만들어요. 그런데 HTML 폼을 submit하면 브라우저가 폼 데이터를 application/x-www-form-urlencoded 형식으로 전송하는데, 이때 공백을 +로 인코딩해요. 오래된 HTTP 폼 방식에서 내려온 관습이에요.

💡

내가 만드는 URL에는 무조건 %20, + 쓰지 마세요

프로그래밍으로 URL 만들 때는 encodeURIComponent()만 쓰면 돼요. 알아서 %20을 써줘요. +는 HTML 폼이 만들어내는 레거시 형식이에요. 직접 생산하면 다른 시스템에서 파싱할 때 문제가 생길 수 있어요.

이중 인코딩 함정 — 가장 많이 빠지는 실수

이중 인코딩이란 이미 인코딩된 문자열을 다시 인코딩하는 거예요. %20이 %2520이 돼버리는 상황이에요. %25가 퍼센트 기호(%) 자체의 인코딩이거든요. 서버에서 %2520을 받으면 %20이라는 문자열로 디코딩해요. 공백이 아니라 '%20'이라는 텍스트가 되는 거죠. 데이터가 깨지는 거예요.

// 잘못된 예: 이미 인코딩된 값을 또 인코딩하면
const alreadyEncoded = 'hello%20world';
const broken = encodeURIComponent(alreadyEncoded);
// → 'hello%2520world' %20의 %가 %25로 인코딩됨

// 올바른 방법: 먼저 디코딩하고 다시 인코딩
const decoded = decodeURIComponent(alreadyEncoded); // → 'hello world'
const correct = encodeURIComponent(decoded); // → 'hello%20world'

function safeEncode(input) {
  try {
    const decoded = decodeURIComponent(input);
    return encodeURIComponent(decoded);
  } catch {
    return encodeURIComponent(input);
  }
}
⚠️

이중 인코딩 — 이럴 때 특히 조심하세요

사용자 입력, DB에서 가져온 값, 외부 API 응답 등 이미 어딘가를 거친 데이터를 URL에 넣을 때 이중 인코딩이 일어나요. 이 값이 혹시 이미 인코딩된 건 아닐까? 항상 먼저 물어보세요. 의심스럽다면 decodeURIComponent()로 한번 디코딩하고 나서 인코딩하세요. 인코딩 함수를 겹쳐 쓰지 마세요.

URL 인코딩 실수 TOP 5

  • 쿼리 값에 encodeURI() 쓰기: &랑 =를 인코딩 안 해서 쿼리 스트링 구조가 조용히 깨져요. 값에는 무조건 encodeURIComponent()예요.
  • 전체 URL에 encodeURIComponent() 쓰기: ://, /, ?, &가 전부 인코딩돼서 URL이 완전히 박살나요. 전체 URL엔 encodeURI()예요.
  • 인코딩 자체를 생략하기: 브라우저가 알아서 해주겠지 싶은데, 서버나 API는 그렇지 않아요. 특히 한글이나 특수문자가 섞인 경우 에러가 나거나 데이터가 깨져요. 보안 취약점으로 이어지기도 해요.
  • 경로 세그먼트에 동적 값 그냥 넣기: /users/홍길동/profile 같은 경로를 그냥 쓰면 서버마다 동작이 달라요. 동적 부분은 encodeURIComponent()로 인코딩하고, / 구분자는 건드리지 마세요.
  • 어떤 건 인코딩하고 어떤 건 안 하기: 일관성 없는 인코딩은 특정 입력에서만 터지는 버그를 만들어요. 동적 값은 항상, 모두 인코딩하는 게 원칙이에요.

자주 묻는 질문

공백이 %20이에요, +예요?

URL에 직접 쓸 때는 %20이 맞아요. HTML 폼이 submit할 때 application/x-www-form-urlencoded 형식으로 보내면 공백이 +로 인코딩돼요. 코드에서 encodeURIComponent()를 쓰면 무조건 %20이 나오니까, 직접 URL 만들 때는 %20으로 통일하면 돼요.

모든 문자를 인코딩해야 하나요?

아니에요. 영문자, 숫자, 그리고 -, _, ., ~ 네 가지 기호는 인코딩 없이 그냥 써도 돼요. /, ?, #, &, = 같은 예약 문자는 URL 구조에서 자기 역할로 쓸 때는 인코딩 안 해도 되고, 쿼리 값 안에 글자로 넣을 때만 인코딩해요. 그 외 한글, 공백, 나머지 특수문자는 인코딩해야 해요.

한글이 왜 %EC%95%88 같은 길고 복잡한 코드로 바뀌나요?

한글 한 글자가 UTF-8 인코딩으로 보통 3바이트예요. 각 바이트를 %XX로 표현하니까 글자 하나에 % 세 개가 붙어요. '안'은 0xEC, 0x95, 0x88이니까 %EC%95%88이 되는 거예요. '안녕하세요' 다섯 글자가 URL에서 열다섯 개의 %XX로 늘어나는 이유예요.

URL 인코딩이랑 HTML 인코딩은 같은 건가요?

완전히 달라요. URL 인코딩은 %20, %26처럼 퍼센트 기호를 써요. HTML 인코딩은 &, < 같은 형태를 써요. URL 인코딩은 URL에서 문자를 안전하게 전송하기 위한 거고, HTML 인코딩은 HTML 문서에서 특수문자를 안전하게 표시하기 위한 거예요. 용도가 다르고 형식도 달라요.

인코딩을 빠뜨리면 보안 문제가 생기나요?

생길 수 있어요. 사용자가 입력한 값을 인코딩 없이 URL에 그냥 넣으면 공개 리다이렉트 공격, XSS 공격, HTTP 헤더 인젝션 같은 취약점이 생길 수 있어요. 인코딩은 편의 기능이 아니라 보안 요구사항으로 봐야 해요.

쿼리 파라미터 여러 개를 URL에 안전하게 넣는 제일 쉬운 방법은요?

URLSearchParams를 쓰는 게 제일 안전하고 편해요. new URLSearchParams({ q: '서울 맛집', page: '2' }) 이렇게 하면 인코딩을 자동으로 올바르게 처리해줘요. 문자열 연결로 직접 URL 만드는 것보다 훨씬 안전하고, 여러 값을 다룰 때 코드도 깔끔해요.

URL 인코더/디코더

URL이나 텍스트를 붙여넣으면 퍼센트 인코딩으로 변환하거나, %XX 코드를 읽을 수 있는 문자로 디코딩할 수 있어요

URL 인코딩/디코딩 해보기

이 글에서 다룬 도구 바로 사용하기

MJ

민재

개발자 겸 테크 라이터. 개발 도구와 파일 변환 기술을 깊이 있게 다룹니다.

이 글이 도움이 되셨나요? 새 가이드 알림 받기

스팸 없이, 새 소식만 보내드립니다. 언제든 취소 가능. · 구독 시 개인정보처리방침에 동의합니다.

이런 글도 좋아하실 수 있어요

84+

제공 도구

100+

블로그 글

English & 한국어

지원 언어

이 페이지를 즐겨찾기하세요! 매주 새로운 무료 도구가 추가됩니다.