웹 개발
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">
최종 체크리스트
- 슬러그가 데이터베이스에 제대로 저장되는가
- URL 인코딩/디코딩이 제대로 되는가
- 슬러그가 유일한가 (충돌이 없는가)
- 조회할 때 같은 방식으로 변환하는가
- 슬러그 변경 시 리다이렉트 또는 canonical을 처리하는가
These checks will resolve most slug-related issues.