← 전체 글로 돌아가기

웹 개발

URL 슬러그 때문에 페이지를 못 찾을 때

URL 슬러그 생성과 조회에서 문제가 발생할 때 원인을 찾는 방법을 정리했다.

URL에 숫자 ID 대신 "my-first-post" 같은 슬러그를 쓰는 것이 자연스럽지만, 슬러그 관련 버그는 찾기 어렵다. 특히 특수문자나 한글이 포함되면 복잡해진다.

슬러그가 제대로 생성되는지 확인

# 데이터베이스에 저장된 슬러그를 본다
SELECT id, title, slug FROM posts LIMIT 5;
# 예상:
# 1 | "My First Post" | "my-first-post"
# 2 | "한글 제목" | "한글-제목"  또는 "-"

한글 제목이 슬러그로 제대로 변환되는지 확인한다:

// Node.js로 슬러그 생성 테스트
import slugify from 'slugify';

console.log(slugify('My First Post')); // "my-first-post"
console.log(slugify('한글 제목')); // "hangul-jeemog" 또는 설정에 따라 다름

한글을 제대로 처리하려면 옵션을 설정해야 한다:

import slugify from 'slugify';

// ❌ 한글이 제거됨
const slug1 = slugify('한글 제목'); // "title"

// ✅ 한글을 보존
const slug2 = slugify('한글 제목', { locale: 'ko' }); // "hangul-jeemog"

// ✅ 한글을 유지하려면
const slug3 = '한글-제목'; // 직접 입력

URL에서 슬러그를 읽을 때 문제

# URL 인코딩
https://example.com/posts/my-first-post  ✅ 정상
https://example.com/posts/my%20first%20post  ❌ 공백이 URL 인코딩됨
https://example.com/posts/한글-제목  ✅ 브라우저가 자동 처리
https://example.com/posts/%ED%95%9C%EA%B8%80-%EC%A0%9C%EB%AA%A9  ❌ URL 인코딩됨

서버에서 디코딩해야 한다:

// Express 라우트
app.get('/posts/:slug', (req, res) => {
  // URL에서 받은 슬러그
  const slug = req.params.slug;

  // URL 디코딩
  const decodedSlug = decodeURIComponent(slug);

  // 데이터베이스 조회
  const post = db.posts.findOne({ slug: decodedSlug });

  if (!post) {
    return res.status(404).json({ error: 'Not found' });
  }

  res.json(post);
});

슬러그 충돌 처리

같은 제목으로 두 개의 게시물을 만들면 슬러그가 같다:

// "My Post"라는 제목 2개
post1.slug = "my-post"
post2.slug = "my-post"  // ❌ 충돌!

해결 방법:

// 방법 1: 숫자 추가
post2.slug = "my-post-2"

// 방법 2: ID 포함
post2.slug = "my-post-2-abc123"

// 방법 3: 슬러그에 ID만 사용 (간단하지만 덜 예쁨)
post.slug = "2"  // 또는 "post-2"

데이터베이스에서 슬러그의 유일성을 보장한다:

ALTER TABLE posts
ADD CONSTRAINT unique_slug UNIQUE(slug);

URL 조회 디버깅

# 실제 요청을 테스트
curl -v 'https://example.com/posts/my-first-post'

# URL이 제대로 도달하는지 서버 로그로 확인
grep "GET /posts" /var/log/nginx/access.log

# 데이터베이스에 그 슬러그가 있는지 확인
SELECT * FROM posts WHERE slug = 'my-first-post';

슬러그 변경 시 리다이렉트

게시물 제목을 바꾸면 슬러그도 바뀐다. 기존 URL이 깨진다:

# 이전
https://example.com/posts/my-first-post

# 제목 변경 후
https://example.com/posts/my-updated-post  # 기존 URL은 404

해결 방법:

// 이전 슬러그를 저장하고 리다이렉트
app.get('/posts/:slug', (req, res) => {
  const post = db.posts.findOne({ slug: req.params.slug });

  if (!post) {
    // 이전 슬러그 확인
    const oldPost = db.posts.findOne({ oldSlug: req.params.slug });
    if (oldPost) {
      return res.redirect(301, `/posts/${oldPost.slug}`);
    }
    return res.status(404);
  }

  res.json(post);
});

또는 SEO를 위해 canonical 링크를 사용한다:

<!-- 이전 URL이어도 canonical은 현재 URL을 가리킨다 -->
<link rel="canonical" href="https://example.com/posts/my-updated-post">

최종 체크리스트

  1. 슬러그가 데이터베이스에 제대로 저장되는가
  2. URL 인코딩/디코딩이 제대로 되는가
  3. 슬러그가 유일한가 (충돌이 없는가)
  4. 조회할 때 같은 방식으로 변환하는가
  5. 슬러그 변경 시 리다이렉트 또는 canonical을 처리하는가

These checks will resolve most slug-related issues.