Toolypet
블로그로 돌아가기
Dev

URL Encoding 완벽 이해 - 한글 깨짐과 특수문자 문제 해결

API 호출에서 한글이 깨지고, 특수문자가 오류를 일으키는 이유. encodeURI vs encodeURIComponent 차이와 실전 해결법.

Toolypet Team

Toolypet Team

Development Team

4 분 읽기

URL Encoding 완벽 이해

"API에 한글 파라미터 보냈더니 깨져서 왔어요."

보낸 것: ?name=홍길동
받은 것: ?name=í길ë

이 문제의 원인은 URL 인코딩입니다. 제대로 이해하면 다시는 깨진 글자로 고생하지 않습니다.


왜 인코딩이 필요한가

URL은 1990년대에 만들어졌고, ASCII 문자만 허용합니다.

URL에서 안전한 문자

A-Z  a-z  0-9  - _ . ~

이게 전부입니다.

URL에서 문제가 되는 문자

문자문제
한글ASCII 아님
공백URL 구분자로 해석
&파라미터 구분자
?쿼리스트링 시작
=키-값 구분자
#fragment 시작
/경로 구분자

인코딩이 하는 일

문제되는 문자를 %XX 형식(16진수)으로 변환합니다.

공백   → %20
&      → %26
홍     → %ED%99%8D (UTF-8 기준 3바이트)

JavaScript 인코딩 함수

핵심 차이점

const text = "hello world&name=홍길동";

encodeURI(text);
// "hello%20world&name=%ED%99%8D%EA%B8%B8%EB%8F%99"
// & 와 = 는 그대로 (URL 구조 유지)

encodeURIComponent(text);
// "hello%20world%26name%3D%ED%99%8D%EA%B8%B8%EB%8F%99"
// & → %26, = → %3D (모두 인코딩)

언제 무엇을 쓰는가

상황함수이유
쿼리 파라미터 encodeURIComponent&, =도 인코딩해야
전체 URLencodeURIURL 구조 유지
경로 세그먼트encodeURIComponent/도 인코딩해야

실수하기 쉬운 케이스

// ❌ 잘못된 사용: 전체 URL을 encodeURIComponent
const url = "https://api.com/search?q=테스트";
encodeURIComponent(url);
// "https%3A%2F%2Fapi.com%2Fsearch%3Fq%3D%ED%85%8C%EC%8A%A4%ED%8A%B8"
// URL로 사용 불가능!

// ✅ 올바른 사용: 값만 인코딩
const query = "테스트";
const url = `https://api.com/search?q=${encodeURIComponent(query)}`;
// "https://api.com/search?q=%ED%85%8C%EC%8A%A4%ED%8A%B8"

URLSearchParams - 현대적 방법

직접 인코딩하는 대신 URLSearchParams를 사용하세요.

기본 사용

const params = new URLSearchParams({
  name: "홍길동",
  city: "서울시 강남구",
  tags: "개발자,프론트엔드"
});

params.toString();
// "name=%ED%99%8D%EA%B8%B8%EB%8F%99&city=%EC%84%9C%EC%9A%B8%EC%8B%9C+%EA%B0%95%EB%82%A8%EA%B5%AC&tags=%EA%B0%9C%EB%B0%9C%EC%9E%90%2C%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C"

자동으로 인코딩됩니다. 실수할 일이 없습니다.

URL 객체와 함께

const url = new URL("https://api.com/search");
url.searchParams.set("q", "홍길동");
url.searchParams.set("page", 1);

url.toString();
// "https://api.com/search?q=%ED%99%8D%EA%B8%B8%EB%8F%99&page=1"

파싱 (디코딩)

const url = new URL("https://api.com/search?name=%ED%99%8D%EA%B8%B8%EB%8F%99");

url.searchParams.get("name");  // "홍길동" ← 자동 디코딩!

실전 문제 해결

문제 1: 이중 인코딩

// 이미 인코딩된 값을 또 인코딩
const encoded = "%ED%99%8D%EA%B8%B8%EB%8F%99";
encodeURIComponent(encoded);
// "%25ED%2599%258D%EA%B8%B8%EB%8F%99"
// % → %25 로 변환되어 완전히 깨짐

해결: 인코딩 전에 이미 인코딩되었는지 확인

function safeEncode(str) {
  try {
    // 이미 인코딩되었다면 디코딩 후 다시 인코딩
    return encodeURIComponent(decodeURIComponent(str));
  } catch {
    // 디코딩 실패 = 인코딩 안 된 상태
    return encodeURIComponent(str);
  }
}

문제 2: 공백의 두 얼굴

%20  vs  +

둘 다 공백이지만 출처가 다릅니다.

방식사용처
%20URL 표준 (RFC 3986)
+HTML 폼 (application/x-www-form-urlencoded)
encodeURIComponent("hello world");  // "hello%20world"
new URLSearchParams({q: "hello world"}).toString();  // "q=hello+world"

둘 다 서버에서 올바르게 처리됩니다. 하지만 일관성을 위해 하나의 방식을 선택하세요.

문제 3: 프레임워크가 이미 인코딩함

axios, fetch, React Router 등은 자동으로 인코딩합니다.

// axios - 자동 인코딩
axios.get('/search', { params: { name: '홍길동' } });
// 요청: /search?name=%ED%99%8D%EA%B8%B8%EB%8F%99

// ❌ 잘못: 직접 인코딩하면 이중 인코딩
axios.get('/search', { params: { name: encodeURIComponent('홍길동') } });
// 요청: /search?name=%25ED%2599%258D... (깨짐)

규칙: 프레임워크를 사용한다면 직접 인코딩하지 마세요.


서버 사이드 처리

Node.js / Express

// 자동 디코딩됨
app.get('/search', (req, res) => {
  const name = req.query.name;  // "홍길동"
});

// 경로 파라미터도 마찬가지
app.get('/users/:name', (req, res) => {
  const name = req.params.name;  // "홍길동"
});

Python / Flask

from flask import request

@app.route('/search')
def search():
    name = request.args.get('name')  # "홍길동"

PHP

$name = $_GET['name'];  // "홍길동"
// PHP도 자동 디코딩

특수 케이스

Base64 in URL

표준 Base64에는 +, /, =가 있어 URL에서 문제됩니다.

// 표준 Base64
btoa("hello");  // "aGVsbG8="

// URL-safe Base64
function base64UrlEncode(str) {
  return btoa(str)
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');  // padding 제거
}

base64UrlEncode("hello");  // "aGVsbG8"

JWT가 이 형식을 사용합니다.

Fragment (#) 주의

https://example.com/page?name=홍길동#section
                                    ↑
                            여기부터 서버로 안 감

# 이후(fragment)는 브라우저에서만 처리되고 서버로 전송되지 않습니다.


디버깅 팁

브라우저 주소창

브라우저는 가독성을 위해 디코딩해서 보여줍니다.

표시: https://example.com/users/홍길동
실제: https://example.com/users/%ED%99%8D%EA%B8%B8%EB%8F%99

개발자 도구 Network 탭에서 실제 요청 URL을 확인하세요.

curl로 테스트

# 직접 인코딩된 URL
curl "https://api.com/search?name=%ED%99%8D%EA%B8%B8%EB%8F%99"

# curl이 인코딩하게
curl -G "https://api.com/search" --data-urlencode "name=홍길동"

요약

상황해결
쿼리 파라미터 만들기URLSearchParams 사용
값만 인코딩encodeURIComponent
전체 URL 인코딩encodeURI
프레임워크 사용 중직접 인코딩 하지 않기
깨진 글자이중 인코딩 의심

핵심 원칙: 가능하면 URLSearchParams나 프레임워크에 맡기고, 직접 인코딩은 피하세요.


관련 도구

도구용도
URL Encoder인코딩/디코딩
URL ParserURL 구조 분석
Base64Base64 변환
URLencodingdecoding웹개발API쿼리스트링

저자 소개

Toolypet Team

Toolypet Team

Development Team

The Toolypet Team creates free, privacy-focused web tools for developers and designers. All tools run entirely in your browser with no data sent to servers.

Web DevelopmentCSS ToolsDeveloper ToolsSEOSecurity