게시판 주소를 /mysite/board/856으로 통일하는 작업을 끝냈다고 생각했습니다. 새 주소로 접속하면 게시판도 잘 열렸고, 목록과 글 상세도 문제없이 보였습니다.
그런데 서치콘솔에서 이상한 주소가 보였습니다. 같은 게시판이 /mysite/board/856뿐 아니라 /mysite/856으로도 잡히고 있었습니다. 두 주소 모두 정상 페이지처럼 열렸고, 응답 코드도 200 OK였습니다.
이 글은 짧은 URL을 만드는 방법만 설명하는 글이 아닙니다. 새 게시판 경로를 만들고도 옛 경로를 닫지 않아 같은 페이지가 두 주소로 열렸던 문제를, 메뉴 타입 조회와 301 리다이렉트로 정리한 과정을 기록한 글입니다.
같은 게시판이 두 주소로 열린 상황
문제가 된 주소는 두 가지였습니다.
/mysite/board/856 → 의도한 게시판 주소
/mysite/856 → 의도하지 않은 예전식 주소겉으로 보면 첫 번째 주소만 쓰면 될 것 같았습니다. 하지만 두 번째 주소도 그대로 열렸습니다. 더 문제는 두 주소의 내용이 같다는 점이었습니다.
검색엔진 입장에서는 같은 게시판 목록이 두 개의 URL로 존재하는 구조입니다.
/mysite/board/856 → 200 OK
/mysite/856 → 200 OK사용자에게는 큰 차이가 없어 보일 수 있습니다. 하지만 SEO 관점에서는 대표 주소가 흐려지고, 중복 페이지 보고가 늘어날 수 있습니다.
리라이트 규칙이 둘 다 살아 있던 구조
.htaccess에는 새 게시판 주소와 일반 페이지 주소를 처리하는 규칙이 같이 들어 있었습니다.
# 게시판 주소
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([A-Za-z0-9_-]+)/board/([0-9]+)/?$ list.php?id=$1&menu=$2 [L,QSA]
# 일반 하위 페이지
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([A-Za-z0-9_-]+)/([0-9]+)$ page.php?id=$1&menu=$2 [L,QSA]/mysite/board/856은 첫 번째 규칙에 걸려 게시판 파일로 이동합니다. /mysite/856은 두 번째 규칙에 걸려 일반 페이지 파일로 이동합니다.
여기까지만 보면 규칙은 정상입니다. 문제는 일반 페이지 파일도 내부적으로 게시판 메뉴를 처리할 수 있었다는 점입니다. 예전 구조에서는 메뉴 번호만 있으면 일반 페이지든 게시판이든 같은 입구에서 처리했기 때문입니다.
결국 새 경로를 만들었지만 옛 경로를 닫지 않은 상태였습니다.
URL만 보고는 게시판인지 알 수 없음
처음에는 .htaccess에서 /mysite/856을 막으려고 했습니다. 하지만 URL만 보고는 856이 게시판인지 일반 페이지인지 알 수 없었습니다.
/mysite/856이 숫자가 일반 페이지 메뉴 번호일 수도 있고, 게시판 메뉴 번호일 수도 있습니다. 정답은 URL에 있지 않고 DB에 있습니다.
Apache 리라이트 규칙은 URL 패턴은 볼 수 있지만 데이터베이스의 menu_type까지 확인할 수는 없습니다. 그래서 이 문제는 .htaccess만으로 해결하기 어렵다고 판단했습니다.
분기는 PHP에서 해야 했습니다.
메뉴 타입 조회 후 표준 경로로 이동
일반 페이지 파일에서 메뉴 정보를 조회한 직후 분기를 넣었습니다. 메뉴 타입이 게시판이면 일반 페이지로 렌더링하지 않고 표준 게시판 주소로 301 이동시켰습니다.
<?php
$stmt = $pdo->prepare(
'SELECT number, menu_type FROM site_menu
WHERE number = ? AND site_id = ? LIMIT 1'
);
$stmt->execute([$menuNo, $siteId]);
$menu = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$menu) {
http_response_code(404);
include __DIR__ . '/error/404.php';
exit;
}
if (strpos($menu['menu_type'], 'board_') === 0) {
$url = 'https://example.com/' . rawurlencode($siteId)
. '/board/' . (int)$menuNo;
header('Location: ' . $url, true, 301);
exit;
}
?>로직은 단순합니다. 메뉴 번호로 실제 메뉴 타입을 조회하고, board_로 시작하는 게시판 타입이면 /site/board/no 형식으로 보냅니다.
일반 페이지 타입이면 리다이렉트하지 않고 기존처럼 렌더링합니다.
처리 흐름 정리
수정 후 흐름은 이렇게 바뀌었습니다.
사용자 또는 봇이 /mysite/856 접근
→ .htaccess가 page.php?id=mysite&menu=856으로 전달
→ PHP에서 menu_type 조회
→ 게시판 타입이면 /mysite/board/856으로 301 이동
→ 표준 게시판 주소만 200 OK 유지반대로 일반 페이지 메뉴라면 그대로 열립니다.
/mysite/1250
→ page.php?id=mysite&menu=1250
→ menu_type이 일반 페이지
→ 200 OK 렌더링이렇게 해야 게시판은 게시판 경로로 모이고, 일반 페이지는 일반 페이지 경로로 남습니다.
없는 메뉴는 404로 처리
메뉴 번호가 존재하지 않는 경우도 같이 정리했습니다. 예전에는 없는 번호로 들어와도 빈 페이지나 기본 페이지가 열리는 경우가 있었습니다.
<?php
if (!$menu) {
http_response_code(404);
include __DIR__ . '/error/404.php';
exit;
}
?>존재하지 않는 메뉴는 301로 어디론가 보내지 않았습니다. 실제 대응할 페이지가 없다면 404가 더 명확합니다.
무조건 메인으로 보내면 검색엔진이 소프트 404로 판단할 수 있고, 사용자도 원하는 내용을 찾지 못한 채 엉뚱한 페이지로 이동하게 됩니다.
canonical도 함께 확인
301을 넣은 뒤에도 canonical을 같이 확인했습니다. 표준 게시판 주소에서는 canonical이 자기 자신을 가리켜야 합니다.
<link rel="canonical" href="https://example.com/mysite/board/856">잘못된 주소인 /mysite/856에서는 canonical을 보여주는 것보다 먼저 301이 발생해야 합니다. 잘못된 URL이 계속 200 OK로 열리고 canonical만 다른 주소를 가리키는 상태보다는, 서버가 직접 표준 주소로 보내는 편이 더 명확했습니다.
새 경로를 열었으면 옛 경로 확인
이번 문제는 새 주소가 잘 되는지만 보고 끝냈기 때문에 생겼습니다. /mysite/board/856이 정상인지 확인했지만, /mysite/856이 여전히 살아 있는지는 놓쳤습니다.
URL 구조를 바꿀 때는 새 주소와 옛 주소를 같이 확인해야 합니다.
curl -I https://example.com/mysite/board/856
curl -I https://example.com/mysite/856원하는 결과는 아래와 같았습니다.
/mysite/board/856 → 200 OK
/mysite/856 → 301 → /mysite/board/856브라우저로만 확인하면 자동으로 이동해서 301 여부를 놓칠 수 있습니다. curl -I로 상태 코드와 Location 헤더를 직접 보는 편이 안전했습니다.
메뉴 타입별 경로 기준
중복을 줄이려면 메뉴 타입별로 표준 경로를 정해두는 것이 좋았습니다.
| 메뉴 타입 | 표준 경로 | 예전 경로 처리 |
|---|---|---|
| 게시판 | /{site}/board/{menu} | /{site}/{menu} → 301 |
| 일반 페이지 | /{site}/{menu} | 그대로 200 |
| 검색 | /{site}/search?q={keyword} | 긴 검색 URL → 301 |
| 없는 메뉴 | 없음 | 404 |
이 기준을 정해두면 나중에 새 메뉴 타입이 생겼을 때도 어디로 보내야 할지 판단하기 쉬워집니다.
적용 후 달라진 점
수정 후에는 같은 게시판이 두 주소로 열리는 문제가 사라졌습니다.
/mysite/board/856만200 OK로 유지/mysite/856은 게시판 타입일 때 표준 주소로 301 이동- 일반 페이지 메뉴는 기존 짧은 주소 유지
- 없는 메뉴 번호는 404 처리
- 서치콘솔 중복 URL 보고 감소
- 잘못된 내부 링크를 발견하기 쉬워짐
특히 좋았던 점은 URL만 보고 애매했던 판단을 DB의 menu_type 기준으로 정리했다는 점입니다. 데이터가 정답을 알고 있다면 서버 설정 파일보다 애플리케이션 코드에서 판단하는 편이 안전했습니다.
메뉴 타입 리다이렉트 체크리스트
주소를 추가하면 닫을 주소도 함께 보기
URL 정리는 새 주소를 만드는 작업으로 끝나지 않았습니다. 새 주소를 열었으면 기존 주소가 어떻게 동작하는지도 반드시 확인해야 했습니다.
이번 문제도 /mysite/board/856을 만든 것 자체는 성공이었습니다. 하지만 /mysite/856이 여전히 같은 게시판을 보여주면서 중복 URL이 생겼습니다.
제가 얻은 기준은 단순합니다. URL만으로 판단할 수 없는 경우에는 DB 값을 기준으로 결정합니다. 메뉴 타입이 게시판이면 게시판 표준 경로로 보내고, 일반 페이지면 그대로 두고, 없는 메뉴면 404로 처리합니다.
검색엔진에게 보여줄 대표 주소는 하나여야 합니다. 새 경로를 만들었다면 옛 경로를 닫는 것까지가 URL 정리 작업입니다.