Blog
HomePostsTagsCategories
© 2026 Hyeonseok An. All rights reserved.
GitHubEmailInstagram
Home
Develop
[TIL] Tag/Category URL Slug 문제 해결 학습 기록

[TIL] Tag/Category URL Slug 문제 해결 학습 기록

August 20, 2025
Loading...
Next.jsError HandlingSEOTILFront-endURL encodingBlogTrouble shooting
Cover image for [TIL] Tag/Category URL Slug 문제 해결 학습 기록

1. 문제 정의 및 분석

1.1 발견된 문제들

이번 스프린트에서 블로그의 태그와 카테고리 URL 시스템에서 두 가지 주요 문제가 발견되었습니다.

문제 1: 대/소문자 구분 이슈

  • 현상: Tag가 "Proxmox"인 경우, 정적으로 생성된 페이지는 /tag/proxmox로 생성됨
  • 문제점: 포스트 미리보기 카드의 tag badge href가 태그 원본 그대로 /tag/Proxmox로 설정
  • 결과: 사용자가 태그를 클릭하면 404 오류 발생

문제 2: 한글 태그/카테고리 URL 접근 실패

  • 현상: "홈서버", "개발 블로그" 등 한글 태그가 포함된 포스트에서 문제 발생
  • 문제점 1: 포스트 미리보기 카드의 tag badge href가 정상적으로 설정되지 않음
  • 문제점 2: /tag/홈서버와 같이 한글로 직접 URL 접근해도 404 발생
  • 근본 원인: 기존 slugify 함수가 한글 문자를 제거해버림

1.2 문제의 근본 원인 분석

1.2.1 slugify 함수의 한계

// 기존 문제가 있던 코드 export function slugify(text: string): string { return text .toLowerCase() .normalize('NFD') // 한글을 자소 단위로 분해 .replace(/[\\u0300-\\u036f]/g, '') // 분해된 자소 제거 .replace(/[^\\w\\s-]/g, '') // 한글 문자까지 제거 .replace(/[\\s_-]+/g, '-') .replace(/^-+|-+$/g, '') }
  • normalize('NFD')가 한글을 자소 단위로 분해 ("홈" → "ㅎ", "ㅗ", "ㅁ")
  • replace(/[^\\w\\s-]/g, '')가 분해된 한글 자소들을 특수문자로 인식하여 제거
  • 결과적으로 한글 태그는 빈 문자열이 되어 fallback으로 "untitled"이 됨

1.2.2 PostCard 컴포넌트의 href 설정 문제

// 문제가 있던 코드 (4개 variant 모두 동일한 패턴) <Link key={tag} href={`/tag/${tag}`} // 원본 태그명 그대로 사용 className={getTagClasses('compact')} > {tag} </Link>
  • 태그 href 생성 시 원본 태그명을 그대로 사용
  • slugify 적용 없이 대/소문자와 특수문자가 그대로 URL에 포함
  • 정적 생성된 페이지 경로와 불일치 발생

1.2.3 URL 디코딩 처리 부재

// 기존 태그 찾기 로직 const tag = tags.find(t => t.slug === slug)
  • 브라우저에서 한글 URL 입력 시 자동으로 URL 인코딩 (홈서버 → %ED%99%88%EC%84%9C%EB%B2%84)
  • 서버에서 받는 slug 파라미터와 DB의 slug 직접 비교로 인한 매칭 실패
  • URL 디코딩이나 정규화 과정 없이 단순 문자열 비교만 수행

1.2.4 정적 페이지 생성과 동적 라우팅 불일치

  • generateStaticParams()에서는 올바른 slug로 정적 페이지 생성
  • 실제 페이지 접근 시에는 URL 파라미터와 DB 데이터 간 매칭 실패
  • 다양한 URL 접근 패턴에 대한 fallback 로직 부재

2. 기술적 배경 지식

2.1 Next.js Dynamic Routing 이해

2.1.1 [slug] 파라미터 처리 방식

Next.js의 동적 라우팅에서 [slug] 파라미터는 URL의 동적 세그먼트를 캡처합니다.
// /tag/[slug]/page.tsx interface TagPageProps { params: Promise<{ slug: string // URL에서 추출된 slug 파라미터 }> }
중요한 점은 브라우저가 URL을 어떻게 처리하느냐입니다:
  • 영문 대소문자: /tag/Proxmox → slug는 "Proxmox" 그대로
  • 한글: /tag/홈서버 → slug는 "홈서버" 또는 URL 인코딩된 형태

2.1.2 generateStaticParams와 ISR의 관계

export async function generateStaticParams() { const tags = await getAllTags() return tags.map(tag => ({ slug: tag.slug, // slugify된 값들 })) } export const revalidate = 3600 // ISR: 1시간마다 재검증
  • 빌드 타임에 모든 태그에 대해 정적 페이지 생성
  • tag.slug는 이미 slugify된 값 (예: "proxmox", "홈서버")
  • ISR로 새로운 태그 추가 시에도 페이지 생성

2.1.3 notFound() 함수 동작 원리

if (!tag) { notFound() // Next.js built-in 404 처리 }
  • 태그를 찾지 못하면 즉시 404 페이지로 리다이렉트
  • 사용자에게는 "This page could not be found" 메시지 표시
  • 이전 로직에서는 대소문자나 URL 인코딩 불일치로 인해 빈번히 발생

2.2 Unicode와 URL 인코딩

2.2.1 한글 문자의 Unicode 범위

// 한국어 완성형 한글 (가-힣) \\uAC00-\\uD7AF // 44032부터 55215까지
주요 한글 Unicode 블록:
  • \uAC00-\uD7AF: 완성형 한글 (가, 나, 다, ... , 힘, 힙, 힣)
  • \u1100-\u11FF: 한글 자모 (ㄱ, ㄴ, ㄷ, ...)
  • \u3130-\u318F: 호환용 한글 자모 (ㄱ, ㄴ, ㄷ, ...)
우리가 주로 사용하는 것은 완성형 한글이므로 \uAC00-\uD7AF 범위가 가장 중요합니다.

2.2.2 URL 인코딩/디코딩

// 한글 "홈서버"의 URL 인코딩 과정 "홈서버" → UTF-8 바이트: [ED 99 88] [EC 84 9C] [EB B2 84] → URL 인코딩: "%ED%99%88%EC%84%9C%EB%B2%84" // JavaScript에서의 처리 encodeURIComponent("홈서버") // "%ED%99%88%EC%84%9C%EB%B2%84" decodeURIComponent("%ED%99%88%EC%84%9C%EB%B2%84") // "홈서버"
브라우저는 주소창에 한글을 입력하면 자동으로 URL 인코딩을 수행하므로, 서버에서는 이를 디코딩해야 합니다.

2.2.3 정규표현식에서의 Unicode 플래그 사용법

// Unicode 플래그 없이 (문제 발생) /[a-z0-9\\s\\uAC00-\\uD7AF-]/g // SyntaxError 발생 가능 // Unicode 플래그와 함께 (올바른 사용법) /[a-z0-9\\s\\uAC00-\\uD7AF-]/gu // 정상 작동 // 하이픈 위치 주의 (문자 클래스 내에서) /[a-z0-9\\s-\\uAC00-\\uD7AF]/gu // 하이픈을 끝에 위치

2.3 Slug 생성과 검증

2.3.1 SEO 친화적 URL 생성 원칙

좋은 slug의 특징:
  • 가독성: 사용자가 URL만 봐도 내용을 추측할 수 있음
  • 일관성: 동일한 규칙으로 생성된 일관된 패턴
  • 간결성: 불필요한 단어나 문자 제거
  • 검색 엔진 최적화: 키워드 포함과 적절한 길이
// 좋은 slug 예시 "Next.js Development" → "nextjs-development" "Docker & Kubernetes" → "docker-kubernetes" "홈서버 구축하기" → "홈서버-구축하기" // 피해야 할 slug "Next.js Development!!!" → "nextjs-development" // 특수문자 제거 " multiple spaces " → "multiple-spaces" // 공백 정규화

2.3.2 다국어 지원을 위한 slug 설계

한국어 블로그에서 고려해야 할 사항:
  • 한글 문자 보존: SEO와 사용자 경험을 위해 한글 유지
  • 영문과의 혼용: "React 개발기"와 같은 혼합 태그 처리
  • 길이 제한: 한글은 바이트 수가 크므로 적절한 길이 제한 필요
// 다국어 slug 생성 전략 export function slugify(text: string): string { const slug = text .toLowerCase() // 한글, 영문, 숫자, 하이픈만 허용 .replace(/[^a-z0-9\\s\\uAC00-\\uD7AF-]/gu, '') .replace(/[\\s_-]+/g, '-') // 공백을 하이픈으로 .replace(/^-+|-+$/g, '') // 앞뒤 하이픈 제거 return slug || 'untitled' // 빈 문자열 fallback }

2.3.3 URL 안전성과 접근성 고려사항

  • 브라우저 호환성: 모든 주요 브라우저에서 정상 작동
  • 서버 인코딩: 서버가 UTF-8을 올바르게 처리하는지 확인
  • 길이 제한: URL 길이 제한 (일반적으로 2000자 이하 권장)
  • 특수문자 처리: URL에서 의미가 있는 문자들 (?&=# 등) 제거

3. 해결 과정

3.1 1단계: 문제 재현 및 디버깅

3.1.1 개발 환경에서의 문제 재현

문제를 체계적으로 파악하기 위해 다음과 같은 테스트를 수행했습니다:
// 테스트 케이스 작성 const testCases = [ { input: "Proxmox", expected: "proxmox" }, { input: "DOCKER", expected: "docker" }, { input: "홈서버", expected: "홈서버" }, { input: "개발 블로그", expected: "개발-블로그" }, { input: "Next.js Development", expected: "nextjs-development" } ];
각 테스트 케이스에 대해:
  1. 태그 뱃지 클릭 시 이동하는 URL 확인
  1. 해당 URL로 직접 접근 시 응답 확인
  1. 정적 생성된 페이지 경로와 비교

3.1.2 브라우저 네트워크 탭을 통한 요청/응답 분석

Chrome DevTools Network 탭에서 관찰한 내용:
대소문자 문제:
클릭한 링크: /tag/Proxmox 서버 응답: 404 Not Found 실제 존재하는 페이지: /tag/proxmox
한글 문제:
브라우저 주소창: /tag/홈서버 실제 요청 URL: /tag/%ED%99%88%EC%84%9C%EB%B2%84 서버에서 받은 slug: "홈서버" (자동 디코딩됨) DB에서 찾는 slug: tag.slug === "untitled" (slugify 결과)

3.1.3 slug 생성 과정 추적

기존 slug 생성 과정을 단계별로 추적:
// "홈서버" 태그의 slug 생성 과정 추적 console.log('1. 원본:', '홈서버'); console.log('2. toLowerCase:', '홈서버'.toLowerCase()); console.log('3. normalize NFD:', '홈서버'.toLowerCase().normalize('NFD')); console.log('4. 특수문자 제거 후:', ''); // 한글이 모두 제거됨 console.log('5. 최종 결과:', 'untitled');
Tags:Next.jsError HandlingSEOTILFront-endURL encodingBlogTrouble shooting