공백이나 특수문자가 포함된 URL을 브라우저에 붙여넣고 깨지는 것을 보는 경험은 개발자라면 누구나 겪습니다. URL 인코딩 — 또는 퍼센트 인코딩 — 은 임의의 데이터를 URL에 안전하게 포함시키는 방법입니다. 정확한 작동 원리와 적용 시점을 설명합니다.

URL에 인코딩이 필요한 이유

URL은 제한된 문자 집합만 사용할 수 있습니다: 문자(A-Z, a-z), 숫자(0-9), 그리고 소수의 특수문자(-, _, ., ~). 이외의 모든 문자는 퍼센트 인코딩이 필요합니다: %에 2자리 16진수 ASCII 코드를 붙인 형태로 대체됩니다.

공백  →  %20
#     →  %23
?     →  %3F
=     →  %3D
&     →  %26
/     →  %2F
+     →  %2B

인코딩 없이는 쿼리 파라미터 안의 공백이 파라미터를 끊어버리고, &는 구분자로 해석됩니다. 인코딩이 구조를 명확하게 만들어줍니다.

온라인 URL 인코더/디코더

ZeroTool URL 인코더/디코더 →

텍스트나 URL을 붙여넣고 즉시 인코딩 또는 디코딩하세요. API 호출 디버깅, 리다이렉트 URL 구성, 난독화된 쿼리 스트링 디코딩에 유용합니다.

encodeURI vs encodeURIComponent

JavaScript에는 URL 인코딩을 위한 두 가지 내장 함수가 있습니다. 잘못 선택하면 버그의 원인이 됩니다.

encodeURI(url)

완전한 URL을 인코딩합니다. URL에서 구조적 의미를 가진 문자는 보존합니다: :, /, ?, #, [, ], @, !, $, &, ', (, ), *, +, ,, ;, =.

encodeURI("https://example.com/search?q=hello world&lang=en")
// "https://example.com/search?q=hello%20world&lang=en"

공백은 %20이 되지만 ?, =, &는 URL 구조의 일부이므로 그대로 유지됩니다.

encodeURIComponent(value)

쿼리 파라미터나 경로 세그먼트로 사용할 단일 값을 인코딩합니다. encodeURI가 보존하는 것을 포함해 ?, #, &, /, =까지 모두 인코딩합니다.

const query = encodeURIComponent("C++ 는 훌륭하고 빠릅니다");
const url = `https://example.com/search?q=${query}`;
// https://example.com/search?q=C%2B%2B%20%EB%8A%94%20%ED%9B%8C%EB%A5%AD%ED%95%98%EA%B3%A0%20%EB%B9%A0%EB%A6%85%EB%8B%88%EB%8B%A4

경험 법칙: 각 쿼리 파라미터 값을 포함해 URL을 부분별로 구성할 때는 encodeURIComponent를 사용하세요. 이미 구조화된 전체 URL 문자열을 인코딩할 때는 encodeURI를 사용하세요.

+ vs %20 차이

HTML 폼 제출은 전통적으로 공백을 %20 대신 +로 인코딩합니다. 이를 application/x-www-form-urlencoded 인코딩이라고 합니다. 쿼리 스트링을 파싱할 때 둘 다 공백으로 처리해야 하지만, 기술적으로 다른 인코딩입니다. 섞어 쓰면 미묘한 버그가 발생할 수 있습니다.

// x-www-form-urlencoded 방식 (폼 제출)
"hello world""hello+world"

// RFC 3986 퍼센트 인코딩 (현대 표준)
"hello world""hello%20world"

URL 디코딩

역방향 연산:

decodeURI("https://example.com/search?q=hello%20world")
// "https://example.com/search?q=hello world"

decodeURIComponent("C%2B%2B%20%EB%8A%94%20%ED%9B%8C%EB%A5%AD%ED%95%A9%EB%8B%88%EB%8B%A4")
// "C++ 는 훌륭합니다"

Python에서:

from urllib.parse import quote, unquote, urlencode, parse_qs

# 값 인코딩
encoded = quote("C++ 는 훌륭하고 빠릅니다")
# "C%2B%2B%20%EB%8A%94%20%ED%9B%8C%EB%A5%AD%ED%95%98%EA%B3%A0%20%EB%B9%A0%EB%A6%85%EB%8B%88%EB%8B%A4"

# 디코딩
decoded = unquote("C%2B%2B%20is%20great%20%26%20fast")
# "C++ is great & fast"

쿼리 스트링을 올바르게 구성하기

문자열 연결로 쿼리 스트링을 직접 만들지 마세요. 플랫폼의 내장 유틸리티를 사용하세요.

JavaScript (브라우저)

const params = new URLSearchParams({
  q: "C++ 는 훌륭하고 빠릅니다",
  lang: "ko",
  page: 1
});

const url = `https://example.com/search?${params}`;
// https://example.com/search?q=C%2B%2B+%EB%8A%94+%ED%9B%8C%EB%A5%AD%ED%95%98%EA%B3%A0+%EB%B9%A0%EB%A6%85%EB%8B%88%EB%8B%A4&lang=ko&page=1

주의: URLSearchParams는 공백에 %20 대신 +(폼 인코딩)를 사용합니다.

Python

from urllib.parse import urlencode

params = {
    "q": "C++ is great & fast",
    "lang": "ko",
    "page": 1
}

query_string = urlencode(params)
url = f"https://example.com/search?{query_string}"

Go

import "net/url"

params := url.Values{}
params.Set("q", "C++ is great & fast")
params.Set("lang", "ko")
url := "https://example.com/search?" + params.Encode()

경로 세그먼트 vs 쿼리 파라미터

인코딩 규칙이 경로 세그먼트와 쿼리 파라미터 사이에 약간 다릅니다. 경로 세그먼트의 /는 “디렉토리 구분자”를 의미합니다. 경로 세그먼트 값에 리터럴 /를 포함하려면 %2F로 인코딩해야 합니다.

/files/2024/report.pdf        →  세 개의 경로 세그먼트
/files/2024%2Freport.pdf      →  두 개의 경로 세그먼트, 두 번째에 슬래시 포함

국제 문자 처리

현대 URL은 **국제화 자원 식별자(IRI)**를 통해 유니코드를 지원합니다. 브라우저는 ASCII가 아닌 문자를 UTF-8 바이트 표현으로 자동 퍼센트 인코딩합니다:

https://example.com/search?q=한국어
→ https://example.com/search?q=%ED%95%9C%EA%B5%AD%EC%96%B4
from urllib.parse import quote

quote("한국어", safe="")
# "%ED%95%9C%EA%B5%AD%EC%96%B4"

흔한 실수

이중 인코딩

이미 인코딩된 문자열을 다시 인코딩하면 잘못된 출력이 됩니다:

// 잘못된 방법: 이미 인코딩된 값을 다시 인코딩
encodeURIComponent("hello%20world")
// "hello%2520world"  — %25는 %를 인코딩한 것

값이 이미 인코딩된 것인지 확실하지 않으면 먼저 디코딩하세요.

리다이렉트 URL 인코딩 누락

쿼리 파라미터에 임베드된 리다이렉트 대상은 완전히 인코딩되어야 합니다:

# 잘못된 방법 — 외부 ?next= 파서가 리다이렉트 URL의 끝을 알 수 없음
https://auth.example.com/login?next=https://app.example.com/page?id=1&view=full

# 올바른 방법
https://auth.example.com/login?next=https%3A%2F%2Fapp.example.com%2Fpage%3Fid%3D1%26view%3Dfull

요약

상황사용 방법
쿼리 파라미터 값 인코딩encodeURIComponent
전체 URL 문자열 인코딩encodeURI
쿼리 스트링 구성URLSearchParams / urlencode
URL 또는 컴포넌트 디코딩decodeURIComponent / unquote
빠른 수동 인코딩/디코딩ZeroTool URL 인코더

URL 인코딩은 세부 사항을 알면 재현하기 어려운 버그 전체를 예방할 수 있는 주제입니다. 의심스러울 때는 인코딩하세요 — 그리고 문자열 연결이 아닌 언어의 내장 유틸리티를 사용하세요.

ZeroTool로 URL을 즉시 인코딩·디코딩하기 →