9. 배포했는데 한글이 다 깨져요... 왜이래 이거
2025년 12월 13일

배포를 하고 나니, 갑자기 이렇게 글자가 깨지는 문제가 생겼습니다.
문제 상황
Vercel 배포 후 상세 페이지에서 한글이 깨져 표시되었습니다.
문제 증상:
- URL:
https://crime-insight.vercel.app/detail/사기(정상) - 페이지 제목:
%EC%82%AC%EA%B8%B0 범죄 분석 보고서(깨짐) - 차트 제목:
요일별 비교 (전체 평균 vs %EC%82%AC%EA%B8%B0)(깨짐) - 드롭다운 메뉴:
%EC%82%AC%EA%B8%B0(깨짐)
URL 인코딩된 문자열이 그대로 화면에 표시되었습니다.
문제 원인 분석
1. 이중 인코딩 발생
문제가 있던 코드:
// app/detail/[category_middle]/page.tsx export async function generateStaticParams() { const records = parseCrimeData(); const categoryMiddleList = extractCategoryMiddleList(records); return categoryMiddleList.map((categoryMiddle) => ({ category_middle: encodeURIComponent(categoryMiddle), // ❌ 문제! })); }
문제 발생 과정:
generateStaticParams에서encodeURIComponent("사기")→"%EC%82%AC%EA%B8%B0"반환- Next.js가 이 값을 받아 다시 URL 인코딩 →
"%25EC%2582%25AC%25EA%25B8%25B0"(이중 인코딩) - 빌드 타임에 정적 페이지 생성 시 인코딩된 값이 그대로 저장됨
- 페이지 렌더링 시
params.category_middle이"%EC%82%AC%EA%B8%B0"로 전달됨 decodeURIComponent를 사용해도 이미 한 번 인코딩된 상태라 제대로 디코딩되지 않음
2. Next.js의 자동 URL 인코딩
Next.js App Router는 generateStaticParams의 반환값을 자동으로 URL 인코딩합니다.
Next.js 내부 동작:
// Next.js 내부 (의사 코드) function generateRoutes(params) { return params.map(param => { // Next.js가 자동으로 URL 인코딩 처리 const encoded = encodeURIComponent(param.category_middle); return `/detail/${encoded}`; }); }
따라서 개발자가 직접 인코딩하면 이중 인코딩이 발생합니다.
3. 빌드 타임 vs 런타임 차이
빌드 타임 (Static Generation):
generateStaticParams실행- 각 경로에 대해 정적 HTML 생성
- 인코딩된 값이 HTML에 포함됨
런타임:
- 브라우저에서 URL 접근
- Next.js가
params를 디코딩하여 전달 - 하지만 이미 이중 인코딩된 상태라 제대로 디코딩되지 않음
해결 방법
1. generateStaticParams 수정
수정된 코드:
// app/detail/[category_middle]/page.tsx export async function generateStaticParams() { const records = parseCrimeData(); const categoryMiddleList = extractCategoryMiddleList(records); // Next.js가 자동으로 URL 인코딩을 처리하므로 원본 값 반환 return categoryMiddleList.map((categoryMiddle) => ({ category_middle: categoryMiddle, // 인코딩하지 않음 })); }
이유:
- Next.js가 자동으로 URL 인코딩을 처리
- 원본 값을 반환하면 Next.js가 적절히 인코딩
- 이중 인코딩 방지
2. 페이지 컴포넌트 안전 처리
수정된 코드:
export default async function DetailPage({ params }: DetailPageProps) { const { category_middle } = await params; // Next.js가 자동으로 디코딩하지만, 이중 인코딩 방지를 위해 안전하게 처리 const categoryName = category_middle.includes('%') ? decodeURIComponent(category_middle) : category_middle; // 나머지 로직... }
이유:
- Next.js가 자동으로 디코딩하지만, 이전 빌드의 인코딩된 값이 남아있을 수 있음
%문자가 있으면 디코딩, 없으면 그대로 사용- 하위 호환성 유지
3. generateMetadata도 동일하게 수정
export async function generateMetadata({ params }: DetailPageProps): Promise<Metadata> { const { category_middle } = await params; // 동일한 안전 처리 const categoryName = category_middle.includes('%') ? decodeURIComponent(category_middle) : category_middle; // 메타데이터 생성... }
해결 결과
빌드 결과 확인
이전 (문제):
└ ● /detail/[category_middle]
├ /detail/%EC%82%AC%EA%B8%B0 ❌ 인코딩된 값
├ /detail/%EA%B0%95%EB%8F%84 ❌ 인코딩된 값
수정 후:
└ ● /detail/[category_middle]
├ /detail/살인기수 ✅ 한글 정상
├ /detail/살인미수등 ✅ 한글 정상
├ /detail/강도 ✅ 한글 정상
└ [+35 more paths] ✅ 모두 한글 정상
페이지 표시 결과
이전:
- 페이지 제목:
%EC%82%AC%EA%B8%B0 범죄 분석 보고서 - 차트 제목:
요일별 비교 (전체 평균 vs %EC%82%AC%EA%B8%B0)
수정 후:
- 페이지 제목:
사기 범죄 분석 보고서 - 차트 제목:
요일별 비교 (전체 평균 vs 사기)
기억해야할 것
1. Next.js의 자동 처리 이해
Next.js App Router는 URL 인코딩/디코딩을 자동으로 처리합니다:
generateStaticParams: 반환값을 자동으로 URL 인코딩params: 자동으로 디코딩하여 전달
따라서 개발자가 직접 인코딩/디코딩할 필요가 없습니다.
2. 이중 인코딩 주의
잘못된 패턴:
// ❌ 이중 인코딩 발생 return { category_middle: encodeURIComponent(value) }; // Next.js가 또 인코딩 → 이중 인코딩
올바른 패턴:
// ✅ 원본 값 반환 return { category_middle: value }; // Next.js가 자동으로 인코딩 → 정상
3. 하위 호환성 고려
기존 빌드의 인코딩된 값이 남아있을 수 있으므로, 안전하게 처리하는 것이 좋습니다:
const categoryName = category_middle.includes('%') ? decodeURIComponent(category_middle) : category_middle;
추가 확인 사항
1. 다른 컴포넌트에서의 인코딩
다른 컴포넌트에서는 여전히 encodeURIComponent를 사용해야 합니다:
// components/CrimeSelector.tsx const handleSelect = (crimeMiddle: string) => { router.push(`/detail/${encodeURIComponent(crimeMiddle)}`); // ✅ 필요 };
이유:
- 클라이언트 사이드에서 URL 생성 시에는 직접 인코딩 필요
- Next.js의 자동 인코딩은 서버 사이드에서만 작동
2. 빌드 후 확인
배포 전 로컬에서 빌드하여 확인:
npm run build # 빌드 결과에서 한글이 정상적으로 표시되는지 확인
결론
이 문제는 Next.js의 자동 URL 인코딩을 이해하지 못해 발생했습니다. generateStaticParams에서 원본 값을 반환하고, Next.js가 자동으로 처리하도록 하면 해결됩니다.
핵심 원칙:
generateStaticParams: 원본 값 반환 (인코딩하지 않음)params: Next.js가 자동 디코딩 (필요시 안전 처리)- 클라이언트 사이드: 직접 인코딩 필요
이제 모든 페이지에서 한글이 정상적으로 표시됩니다.
그리고 다시 재 배포를 진행했습니다.

ssg로 모든 페이지를 빌드시점에 다 잘 만드는 것을 확인할 수 있습니다.
설정관련해서는 지난 글들에서 구현하며 설명했는데, 이제 넥스트 자체가 기본적으로 app router를 사용하므로, 동적 라우팅 상황에서만 generateStaticParams 및 dynamicParams = false를 해주면 ssg로 모든 페이지를 빌드시점에 만들 수 있습니다.
이렇게 배포를 완료하였습니다.
프로젝트 일단은 끝~ 혹시나 부족한점이나 잘못된 점들이 있으면 추후 수정할 예정입니다.