한 줄: Next.js에서 trailingSlash: true만 켜면 OG·PNG 같은 정적 자원이 308로 붙어 네이버 Yeti 등이 이미지를 못 가져갈 수 있다. skipTrailingSlashRedirect: true와 canonical 슬래시 통일이 세트다.
콘텐츠 URL을 /post/foo/ 형태로 맞추면 sitemap·내부 링크 중복을 줄이기 쉽다. 문제는 프레임워크가 /opengraph-image·확장자 있는 정적 경로에도 trailing slash 리다이렉트를 걸 때다. 일부 검색·SNS 크롤러는 OG 수집 단계에서 redirect를 따라가지 않는다. 배포 전 curl -sI로 이미지 URL이 200인지 확인하고, 메타의 절대 URL도 슬래시 정책과 맞춘다.
Next.js trailingSlash: true는 페이지 경로 끝에 /를 붙인다. 팀·호스팅·기존 백링크가 슬래시 있는 형태면 중복 색인 위험을 낮춘다.
- canonical, sitemap, RSS 링크를 모두
/slug/로 통일하기 쉽다.
- 반대로 슬래시 없는 링크로 들어온 요청은 308/301로 붙을 수 있다.
- App Router의
opengraph-image 특수 라우트, public/*.png, API 경로까지 같은 규칙이 적용되면 부작용이 난다.
Next 15 기능 전반은 Next.js 15 새 기능 가이드, 라우터 이전은 App Router 마이그레이션을 보면 되고, 이 글은 OG·정적 자산 redirect만 깊게 본다.
메타에 og:image로 https://example.com/path/opengraph-image를 넣었는데, 서버가 /opengraph-image/로 308을 주면 일부 수집기는 본문 HTML만 가져가고 이미지 단계에서 멈춘다. 네이버 Yeti 등에서 “본문은 있는데 썸네일 없음”으로 나타나는 패턴과 맞다.
| 요청 URL |
trailingSlash만 true |
+ skipTrailingSlashRedirect |
페이지 /post/foo |
→ /post/foo/ 리다이렉트 가능 |
둘 다 200 가능 (중복 방지 정책 별도) |
OG .../opengraph-image |
→ slash 붙이며 308 위험 |
슬래시 없이 200 유지에 유리 |
/og-default.png |
확장자 경로도 정책에 말릴 수 있음 |
정적 파일 200 우선 |
| 크롤러 OG 수집 |
redirect non-follow 시 실패 |
직접 200으로 수신 |
# 라이브/프리뷰에서 상태 코드만 확인
curl -sI "https://example.com/some-slug/opengraph-image" | head -15
curl -sI "https://example.com/og-default.png" | head -10
curl -sI "https://example.com/favicon.ico" | head -10
# 308/301 이면 Location 헤더를 반드시 확인
curl -sI "https://example.com/some-slug/opengraph-image" | grep -iE 'HTTP/|location:'
Pages Router·App Router 모두 설정 키 이름은 동일 계열이다. 문서 버전에 맞게 확인한다.
/** @type {import('next').NextConfig} */
const nextConfig = {
trailingSlash: true,
// 정적·OG 자원에 강제 trailing slash 308을 막기 위한 세트
skipTrailingSlashRedirect: true,
images: {
unoptimized: true, // 프로젝트 정책에 맞게
},
}
module.exports = nextConfig
skipTrailingSlashRedirect: true는 “슬래시 있는 URL과 없는 URL을 둘 다 200으로 받을 수 있게” 만드는 쪽에 가깝다. 그래서 canonical을 모든 페이지에서 슬래시 포함으로 명시하지 않으면 검색엔진이 두 URL을 따로 볼 위험이 있다.
generateMetadata / Head의 alternates.canonical을 https://도메인/slug/로 고정
- sitemap·RSS의 모든
<loc>도 동일 규칙
- 내부
Link href도 trailing slash 포함 습관
피드 생성과 맞추는 방법은 prebuild sitemap·RSS를 따른다. 프로덕션 체크 항목은 App Router 프로덕션 체크리스트와 겹친다.
redirect를 없애도 메타가 상대 경로이거나 잘못된 호스트면 미리보기가 깨진다.
// 개념 예시 (App Router metadata)
export const metadata = {
alternates: { canonical: 'https://tech.example.com/my-post/' },
openGraph: {
url: 'https://tech.example.com/my-post/',
images: [{ url: 'https://tech.example.com/images/thumbnails/123.png', width: 1200, height: 630 }],
locale: 'ko_KR',
type: 'article',
},
twitter: { card: 'summary_large_image' },
}
- OG 이미지는 가급적 확장자 있는 정적 PNG/JPG를 쓰면 특수 라우트 redirect 이슈를 피하기 쉽다.
- 동적
opengraph-image를 쓸 때도 메타에 적은 URL로 curl -sI 200을 확인한다.
- CDN 캐시가 옛 308을 물고 있으면 퍼지 후 재검증한다.
호스팅이 Vercel이면 프리빌트 업로드 이슈와 별개로, 응답 헤더만으로도 충분히 재현된다. 배포 파이프라인은 prebuilt 업로드·한도는 무료 플랜 한도를 참고한다.
설정 변경 후 프리뷰 URL에서 표를 한 바퀴 돈다.
- 페이지 HTML 200, canonical에 trailing slash
og:image 절대 URL 추출 후 그 URL이 200 (308 아님)
- 슬래시 없는 페이지 URL 정책: 의도한 redirect 또는 200+canonical
- sitemap 해당 slug가 slash 포함인지
- SNS 디버거(지원되는 경우) 캐시 갱신
BASE=https://your-preview.example
# HTML에서 og:image 뽑기
curl -sS "$BASE/my-post/" | grep -oE 'property="og:image"[^>]+|content="https://[^"]+"' | head -20
# 이미지 직접
IMG=$(curl -sS "$BASE/my-post/" | grep -oE 'https://[^"]+/[^"]+.(png|jpg|jpeg|webp)' | head -1)
curl -sI "$IMG" | head -10
로컬 next dev와 프로덕션 빌드 동작이 다를 수 있다. 최종 판정은 프리뷰/프로덕션 응답으로 한다.
- trailingSlash만 켜고 skip 미설정 → OG·PNG 308 사고
- skip만 켜고 canonical 누락 → 슬래시 유무 중복 색인
- sitemap만 옛 규칙 → 본문 canonical과 loc 불일치
- 상대 경로 og:image → 크롤러·일부 플랫폼 실패
- 미들웨어에서 또 redirect → next.config와 이중 정책. middleware matcher에서 정적·이미지 확장자 제외
// middleware에서 정적 자산 제외 예시 (개념)
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.png$|.*\\.jpg$).*)'],
}
※ Next.js 마이너 버전마다 플래그 기본값·헤더 코드가 달라질 수 있다. 릴리스 노트와 실측 curl을 우선한다.
프레임워크·호스팅 기본값은 버전마다 다릅니다. 프리뷰 URL에서 curl 상태 코드로 최종 확인하는 습관을 권장합니다.