Next.js Sitemap과 robots.txt 설정 가이드: 검색 엔진에 웹사이트를 빠르게 등록하는 방법

웹사이트 개발을 마치고 배포한 뒤 신나게 Google에서 사이트 이름을 검색했는데, 결과가 아무것도 나오지 않습니다.
몇 번 새로고침하고 검색어도 바꿔 봤지만 여전히 빈 화면입니다. Google Search Console을 열어 보니 제출한 Sitemap에 ‘가져올 수 없음’이 표시됩니다. 순간 가슴이 철렁합니다. 지금까지 한 일이 모두 헛수고가 된 것 아닐까요? 검색 유입이 없다면 아무리 좋은 콘텐츠도 혼자 감상하는 데 그칠 뿐입니다.
저도 전에 이런 문제를 겪었습니다. 처음 Next.js 프로젝트를 만들었을 때 어느 튜토리얼을 따라 Sitemap을 설정했지만 Google이 전혀 가져가지 못했습니다. 일주일 동안 온갖 방법을 시도한 끝에 robots.txt 설정이 문제라는 사실을 발견했습니다. 사이트 전체를 통째로 차단해 둔 것입니다. 너무 기초적인 실수를 했다는 생각에 맥이 빠졌습니다.
이 글에서는 제가 겪은 시행착오, 찾아본 문서, 직접 시험한 설정을 모두 정리합니다. 두 파일의 역할을 가장 이해하기 쉬운 방식으로 설명하고, Sitemap을 생성하는 세 가지 방법과 자주 발생하는 오류를 알려 드리겠습니다. 마지막에는 실제 실패 사례와 복구 과정도 공유합니다. 사이트가 색인되지 않거나 Sitemap 오류가 발생하거나 동적 페이지 설정 방법을 모르겠다면, 이 글이 시행착오를 크게 줄여 줄 것입니다.
Sitemap과 robots.txt가 필요한 이유
Sitemap의 역할
Sitemap은 검색 엔진에 제공하는 ‘지도’와 같습니다. 웹사이트에 어떤 페이지가 있는지, 얼마나 자주 업데이트되는지, 어떤 페이지가 더 중요한지를 알려 줍니다. Sitemap이 없으면 검색 엔진은 크롤러로 페이지를 하나씩 찾아야 합니다. 특히 깊은 단계에 있는 페이지나 동적으로 생성된 콘텐츠는 몇 달이 지나도 발견되지 않을 수 있습니다.
업계 데이터에 따르면 Sitemap이 있는 웹사이트는 색인 속도가 약 40% 빨라질 수 있습니다. 새 웹사이트에서는 이 차이가 더 두드러집니다. 일주일 안에 색인되는 것과 한 달 뒤에야 색인되는 것만큼 차이가 날 수 있습니다.
robots.txt의 역할
robots.txt는 검색 엔진에 ‘무엇을 크롤링해도 되고 무엇을 크롤링하면 안 되는지’ 알려 줍니다. 관리자 페이지, API 엔드포인트, 빌드 파일 등은 색인할 필요가 없습니다. robots.txt를 설정하면 크롤러가 이런 콘텐츠를 건너뛰고 실제로 중요한 페이지에 크롤링 예산을 사용할 수 있습니다.
하지만 꼭 기억해야 할 점이 있습니다. robots.txt를 설정하지 않는 것이 잘못 설정하는 것보다 낫습니다. 특정 디렉터리를 차단하려다가 실수로 사이트 전체를 차단해 Google에서 사라지는 경우를 너무 많이 봤습니다. 따라서 설정할 때는 반드시 테스트하고, 또 테스트하고, 다시 테스트해야 합니다.
Next.js에서 Sitemap을 생성하는 세 가지 방법
Next.js 13+에서 App Router가 도입되면서 Sitemap 생성 방식도 달라졌습니다. 이제 간단한 방법부터 복잡한 방법까지 세 가지를 차례로 살펴보겠습니다.
방법 1: App Router 기본 sitemap.ts
적합한 경우: 페이지가 수십 개에서 수백 개 정도인 Next.js 13+ 프로젝트
공식적으로 권장되는 방식이며, 별도의 의존성을 설치하지 않아도 된다는 점이 가장 큰 장점입니다. app 디렉터리에 sitemap.ts 파일을 만듭니다.
// app/sitemap.ts
import { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: 'https://yourdomain.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1,
},
{
url: 'https://yourdomain.com/about',
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: 'https://yourdomain.com/blog',
lastModified: new Date(),
changeFrequency: 'weekly',
priority: 0.5,
},
]
}
배포한 뒤 https://yourdomain.com/sitemap.xml에 접속하면 생성된 Sitemap을 확인할 수 있습니다.
장점:
- 공식 지원으로 안정적이고 신뢰할 수 있습니다.
- 별도의 의존성이 필요하지 않습니다.
- TypeScript 타입 검사를 사용할 수 있습니다.
단점:
- 정적 페이지를 수동으로 관리해야 합니다.
- 동적 페이지 데이터는 코드에서 가져와야 합니다.
방법 2: next-sitemap 패키지
적합한 경우: 자동화, 여러 환경 지원이 필요하거나 페이지가 매우 많은 프로젝트
next-sitemap은 커뮤니티에서 가장 널리 쓰이는 Sitemap 생성 도구이며 기능도 매우 강력합니다.
설치:
npm install next-sitemap
설정 파일 next-sitemap.config.js:
/** @type {import('next-sitemap').IConfig} */
module.exports = {
siteUrl: process.env.SITE_URL || 'https://yourdomain.com',
generateRobotsTxt: true, // robots.txt 자동 생성
sitemapSize: 50000, // Sitemap 하나당 최대 URL 50,000개
exclude: ['/admin/*', '/api/*', '/secret'], // 특정 경로 제외
robotsTxtOptions: {
policies: [
{
userAgent: '*',
allow: '/',
disallow: ['/admin', '/api'],
},
],
additionalSitemaps: [
'https://yourdomain.com/server-sitemap.xml', // 동적 Sitemap
],
},
}
package.json에 스크립트 추가:
{
"scripts": {
"build": "next build",
"postbuild": "next-sitemap"
}
}
이제 npm run build를 실행할 때마다 Sitemap이 자동으로 생성됩니다.
장점:
- 기능이 강력하고 여러 Sitemap으로 분할할 수 있습니다.
- robots.txt를 자동으로 생성합니다.
- 동적 라우트를 지원합니다.
- 여러 환경을 설정할 수 있습니다.
단점:
- 별도의 의존성이 필요합니다.
- 설정이 상대적으로 복잡합니다.
방법 3: API 라우트에서 직접 생성
적합한 경우: 매우 세밀한 사용자 정의가 필요하거나 Sitemap을 실시간으로 갱신해야 하는 경우
App Router에서 Route Handler를 사용합니다.
// app/sitemap.xml/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
// 데이터베이스 또는 CMS에서 데이터 가져오기
const posts = await fetchAllPosts()
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://yourdomain.com</loc>
<lastmod>${new Date().toISOString()}</lastmod>
<priority>1.0</priority>
</url>
${posts.map(post => `
<url>
<loc>https://yourdomain.com/blog/${post.slug}</loc>
<lastmod>${post.updatedAt}</lastmod>
<priority>0.7</priority>
</url>
`).join('')}
</urlset>`
return new NextResponse(sitemap, {
status: 200,
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate',
},
})
}
장점:
- 모든 내용을 직접 제어할 수 있습니다.
- 실시간으로 생성할 수 있습니다.
- 복잡한 로직을 추가할 수 있습니다.
단점:
- XML 형식을 직접 작성해야 합니다.
- 성능을 직접 최적화해야 합니다.
- 캐시를 직접 처리해야 합니다.
세 가지 방법 비교
| 방법 | 적합한 경우 | 난이도 | 유연성 | 추천도 |
|---|---|---|---|---|
| App Router 기본 방식 | 소규모 정적 웹사이트 | ⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
| next-sitemap | 중대형 프로젝트 | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| API 라우트 | 매우 세밀한 사용자 정의가 필요한 경우 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
제 프로젝트에서는 대부분 next-sitemap을 사용합니다. 한 번 설정하면 계속 관리할 필요가 없고 기능도 충분하기 때문입니다.
동적 라우트 실전: 블로그 게시물 Sitemap
블로그 게시물, 상품 페이지, 사용자 프로필 같은 동적 콘텐츠를 Sitemap에 어떻게 넣어야 할까요? 가장 흔히 마주치는 상황입니다.
App Router 기본 방식 사용
// app/sitemap.ts
import { MetadataRoute } from 'next'
import { getAllPosts } from '@/lib/posts'
export default async function sitemap(): MetadataRoute.Sitemap {
// 정적 페이지
const staticPages = [
{
url: 'https://yourdomain.com',
lastModified: new Date(),
changeFrequency: 'yearly' as const,
priority: 1,
},
{
url: 'https://yourdomain.com/about',
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
},
]
// 모든 게시물을 동적으로 가져오기
const posts = await getAllPosts()
const postPages = posts.map(post => ({
url: `https://yourdomain.com/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'weekly' as const,
priority: 0.7,
}))
return [...staticPages, ...postPages]
}
// 재검증 시간 설정(ISR)
export const revalidate = 3600 // 한 시간마다 다시 생성
핵심 사항:
changeFrequency와priority에는as const타입 단언이 필요합니다.export const revalidate로 증분 정적 재생성(ISR)을 활성화합니다.lastModified에는 게시물의 실제 수정 시간을 사용하는 것이 좋습니다.
대규모 웹사이트: URL이 50,000개를 넘으면 어떻게 하나요?
Google은 Sitemap 하나에 URL을 최대 50,000개까지 넣을 수 있도록 제한합니다. 이를 넘으면 여러 Sitemap으로 분할해야 합니다.
generateSitemaps 함수를 사용합니다.
// app/sitemap.ts
import { MetadataRoute } from 'next'
// 여러 Sitemap 생성
export async function generateSitemaps() {
const totalPosts = await getTotalPostsCount()
const sitemapsCount = Math.ceil(totalPosts / 50000)
return Array.from({ length: sitemapsCount }, (_, i) => ({
id: i,
}))
}
// 각 Sitemap의 콘텐츠 생성
export default async function sitemap({
id,
}: {
id: number
}): Promise<MetadataRoute.Sitemap> {
const start = id * 50000
const end = start + 50000
const posts = await getPosts(start, end)
return posts.map(post => ({
url: `https://yourdomain.com/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
priority: 0.7,
}))
}
그러면 여러 Sitemap이 생성됩니다.
sitemap/0.xmlsitemap/1.xmlsitemap/2.xml- …
Next.js는 모든 하위 Sitemap 링크를 포함한 sitemap.xml 인덱스 파일을 자동으로 생성합니다.
robots.txt 전체 설정
기본 설정 예시
가장 간단한 robots.txt는 다음과 같습니다.
# 모든 크롤러가 모든 콘텐츠를 크롤링하도록 허용
User-agent: *
Allow: /
# Sitemap 위치 지정
Sitemap: https://yourdomain.com/sitemap.xml
하지만 실제 프로젝트에서는 더 세밀한 설정이 필요합니다.
User-agent: *
Allow: /
# 다음 디렉터리의 크롤링 차단
Disallow: /_next/
Disallow: /api/
Disallow: /admin/
Disallow: /dashboard/
# 특정 파일 형식의 크롤링 차단
Disallow: /*.json$
Disallow: /*.xml$
Disallow: /*?* # 쿼리 매개변수가 있는 URL
# Sitemap 지정
Sitemap: https://yourdomain.com/sitemap.xml
핵심 설명:
/_next/: 검색 엔진이 크롤링할 필요가 없는 Next.js 빌드 파일입니다./api/: API 엔드포인트는 색인할 필요가 없습니다./admin/과/dashboard/: 관리자 페이지는 당연히 색인되면 안 됩니다./*.json$: JSON 파일은 색인할 필요가 없습니다.- Sitemap 줄은 매우 중요합니다. 이 줄을 빠뜨리지 않아야 검색 엔진이 Sitemap 위치를 알 수 있습니다.
Next.js에서 robots.txt 동적 생성
App Router에서는 robots.ts를 사용합니다.
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
const baseUrl = 'https://yourdomain.com'
// 개발 환경에서는 모든 크롤러 차단
if (process.env.NODE_ENV === 'development') {
return {
rules: {
userAgent: '*',
disallow: '/',
},
}
}
// 프로덕션 환경 설정
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: [
'/_next/',
'/api/',
'/admin/',
'/dashboard/',
],
},
{
userAgent: 'GPTBot', // OpenAI 크롤러 차단
disallow: ['/'],
},
],
sitemap: `${baseUrl}/sitemap.xml`,
}
}
환경별 설정의 장점:
개발 환경과 미리보기 환경은 검색 엔진에 색인되면 안 됩니다. 환경 변수로 제어하면 테스트 콘텐츠가 색인되는 일을 막을 수 있습니다.
특히 강조하고 싶은 점이 있습니다. 많은 사람이 프로덕션 환경과 개발 환경에서 같은 설정 파일을 사용하는데, 이는 매우 위험합니다. 다음과 같이 설정하는 것을 권장합니다.
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://yourdomain.com'
const isProduction = process.env.NODE_ENV === 'production'
const isDeployPreview = process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview'
// 프로덕션 이외의 환경에서는 크롤러 완전 차단
if (!isProduction || isDeployPreview) {
return {
rules: {
userAgent: '*',
disallow: '/',
},
}
}
// 프로덕션 환경에서만 크롤러 허용
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: [
'/_next/',
'/api/',
'/admin/',
'/dashboard/',
'/*.json$',
],
},
{
userAgent: 'GPTBot',
disallow: ['/'],
},
],
sitemap: `${baseUrl}/sitemap.xml`,
}
}
이렇게 하면 ‘검색 엔진이 테스트 콘텐츠를 실수로 크롤링하는 상황’이나 ‘프로덕션 환경이 뜻하지 않게 차단되는 상황’을 피할 수 있습니다.
자주 발생하는 오류와 예방법
오류 1: 사이트 전체를 실수로 차단함
# ❌ 잘못된 설정
User-agent: *
Disallow: /
이렇게 작성하면 웹사이트 전체가 차단됩니다. 올바른 설정은 다음과 같습니다.
# ✅ 올바른 설정
User-agent: *
Allow: /
Disallow: /admin/
오류 2: Sitemap 참조를 추가하지 않음
Sitemap을 설정하고도 robots.txt에 선언하지 않아 검색 엔진이 Sitemap 위치를 알지 못하는 경우가 많습니다.
# ❌ 다음 줄이 누락됨
Sitemap: https://yourdomain.com/sitemap.xml
오류 3: 잘못된 경로 형식
# ❌ 오류: 경로가 /로 시작하지 않음
Disallow: _next/
# ✅ 올바름: 경로는 반드시 /로 시작해야 함
Disallow: /_next/
오류 4: 지나친 제한
# ❌ 지나친 제한: 이미지까지 모두 차단함
Disallow: /*.jpg$
Disallow: /*.png$
이미지도 콘텐츠의 일부이므로 특별한 이유가 없다면 검색 엔진이 크롤링하도록 허용해야 합니다.
테스트 방법:
https://yourdomain.com/robots.txt에 접속해 내용이 올바른지 확인합니다.- Google Search Console의 robots.txt 테스트 도구를 사용합니다.
- 특정 URL의 크롤링이 허용되는지 테스트합니다.
Google Search Console 연동 및 검증
Sitemap과 robots.txt를 설정한 뒤에는 Google Search Console에 제출해야 합니다. 그래야 Google이 웹사이트를 더 빠르게 발견하고 색인할 수 있습니다.
Search Console에 웹사이트 추가
- Google Search Console에 접속합니다.
- ‘속성 추가’를 클릭합니다.
- ‘도메인’ 또는 ‘URL 접두어’ 확인 방식을 선택합니다.
DNS 확인 권장:
- 도메인 제공업체에서 TXT 레코드를 하나 추가합니다.
- DNS가 적용될 때까지 몇 분 기다립니다.
- Search Console로 돌아가 확인을 클릭합니다.
또는 HTML 파일로 확인:
Google에서 제공한 확인 파일을 public 디렉터리에 넣습니다. 예: google1234567890abcdef.html.
Sitemap 제출
확인이 완료되면 다음과 같이 진행합니다.
- 왼쪽 메뉴에서 ‘Sitemaps’를 클릭합니다.
- Sitemap URL로
sitemap.xml을 입력합니다. - ‘제출’을 클릭합니다.
대기 시간:
- 제출 직후 Google이 바로 처리하지는 않습니다.
- 보통 1~7일 안에 크롤링을 시작합니다.
- Search Console에서 크롤링 상태를 확인할 수 있습니다.
자주 발생하는 오류 해결
오류 1: ‘Sitemap을 가져올 수 없음’
가장 흔한 문제이며, 다음과 같은 원인이 있을 수 있습니다.
원인 1: Middleware가 Googlebot을 차단함
Next.js Middleware로 인증을 처리한다면 Googlebot도 함께 차단될 수 있습니다.
해결 방법:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
const userAgent = request.headers.get('user-agent') || ''
// 검색 엔진 크롤러인지 확인
const isBot = /googlebot|bingbot|slurp|duckduckbot|baiduspider|yandexbot/i.test(
userAgent
)
// Sitemap과 robots.txt는 크롤러를 포함한 모든 사용자가 접근할 수 있어야 함
if (
pathname === '/robots.txt' ||
pathname === '/sitemap.xml' ||
pathname.startsWith('/sitemap-')
) {
return NextResponse.next()
}
// 크롤러라면 전체 허용
if (isBot) {
return NextResponse.next()
}
// 일반 사용자 인증 로직
const token = request.cookies.get('session-token')
if (!token && pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
}
이 설정은 크롤러가 Sitemap과 robots.txt에 정상적으로 접근하도록 보장하면서 일반 사용자용 인증 로직은 그대로 유지합니다.
원인 2: 캐시 문제
Google Search Console은 실패한 크롤링 시도를 캐시합니다. 문제를 해결한 뒤에도 ‘가져올 수 없음’이 계속 표시될 수 있습니다.
해결 방법:
- Sitemap URL 뒤에 타임스탬프 매개변수를 붙입니다. 예:
sitemap.xml?v=20231220. - Google이 다시 크롤링할 때까지 며칠 기다립니다.
- 또는 기존 Sitemap 제출을 삭제하고 새로 제출합니다.
원인 3: XML 형식 오류
Sitemap XML 형식이 올바른지 확인합니다.
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://yourdomain.com</loc>
<lastmod>2024-12-20</lastmod>
</url>
</urlset>
주의:
- 첫 줄의 XML 선언은
<xml이 아니라<?xml로 시작해야 합니다. lastmod형식은 반드시 ISO 8601(YYYY-MM-DD 또는 전체 타임스탬프)이어야 합니다.
온라인 XML 검증 도구로 형식을 확인할 수 있습니다.
오류 2: ‘제출했지만 색인되지 않음’
Sitemap 제출에는 성공했지만 페이지가 계속 색인되지 않는다면 다음과 같은 원인이 있을 수 있습니다.
- robots.txt가 페이지를 차단함: robots.txt 설정에 오류가 없는지 확인합니다.
- 페이지 품질 문제: 콘텐츠가 너무 짧거나, 중복 콘텐츠이거나, 품질이 낮다고 판단될 수 있습니다.
- 사이트가 너무 새로움: 새 사이트는 신뢰를 쌓는 데 시간이 필요합니다.
- 외부 링크가 없음: 외부 링크가 전혀 없는 사이트는 색인되기 어렵습니다.
해결 방법:
- Google Search Console의 ‘URL 검사’ 도구로 Google이 페이지를 어떻게 인식하는지 확인합니다.
- 페이지에
noindexmeta 태그가 있는지 확인합니다. - 페이지 콘텐츠에 실질적인 가치가 있고 최소 300자 이상인지 확인합니다.
- 외부 링크를 확보해 봅니다.
색인 상태 모니터링
제출한 뒤에는 색인 상태를 정기적으로 확인합니다.
- Coverage 보고서: 어떤 페이지가 색인되었고 어떤 페이지에 문제가 있는지 확인합니다.
- 색인 상태: 전체 색인 페이지 수를 확인합니다.
- Sitemap 상태: Sitemap이 정상적으로 읽히는지 확인합니다.
색인되지 않은 페이지를 발견하면 ‘URL 검사’ 도구에서 Google에 다시 크롤링해 달라고 요청합니다.
실전 사례: 제가 겪은 시행착오
여기서 실제 경험 하나를 공유하겠습니다. 작년에 맡게 된 한 전자상거래 사이트는 이미 배포한 지 3개월이 지났지만 Google 검색에서 어떤 페이지도 찾을 수 없었습니다.
가장 먼저 robots.txt를 확인했더니 다음과 같이 설정되어 있었습니다.
User-agent: *
Disallow: /
사이트 전체가 차단되어 있었습니다. 더 뼈아픈 점은 이 설정이 3개월 동안 유지되었다는 것입니다. 배포 담당자에게 물으니 ‘제가 바꾼 게 아닌데요’라고 했습니다. 결국 임시 개발자가 검색 엔진이 테스트 환경을 크롤링하지 못하도록 만든 설정을 프로덕션 환경에도 그대로 사용했다는 사실을 알아냈습니다.
그 뒤 한 달은 정말 답답했습니다. 잘못된 robots.txt를 삭제하고 올바른 Sitemap을 생성해 Google Search Console에 제출한 뒤 기다릴 수밖에 없었습니다. 일주일이 지나서야 사이트가 검색 결과에 나타나기 시작했습니다.
그 이후로는 습관이 하나 생겼습니다. 배포 전에 항상 Sitemap과 robots.txt를 확인하고 환경별로 설정을 분리합니다. 개발 환경과 프로덕션 환경에 각각 다른 설정을 사용합니다.
Middleware 때문에 문제가 생긴 적도 있습니다. 웹사이트에 JWT 인증 미들웨어를 추가했는데 Googlebot까지 차단되었습니다. 당시 Search Console에서 계속 ‘Sitemap을 가져올 수 없음’ 오류가 발생해 Sitemap 형식이 잘못된 줄 알았습니다. 한참을 씨름하고서야 Middleware가 원인임을 발견했습니다.
SEO는 단순해 보이지만 세부 설정이 정말 중요합니다. 작은 설정 오류 하나만으로도 웹사이트가 검색 결과에서 사라질 수 있습니다.
문제 해결 및 최적화
테스트 체크리스트
배포 전에 다음 항목을 차례로 확인하세요.
- Sitemap에 정상적으로 접근할 수 있음(
https://yourdomain.com/sitemap.xml) - Sitemap XML 형식이 올바름
- robots.txt에 정상적으로 접근할 수 있음(
https://yourdomain.com/robots.txt) - robots.txt가 중요한 페이지를 차단하지 않음
- robots.txt에 Sitemap 참조가 포함됨
- Sitemap에 모든 중요 페이지가 포함됨
- 동적 페이지가 Sitemap에 자동으로 반영됨
- Search Console에서 웹사이트 확인을 완료함
- Search Console에 Sitemap을 제출함
- Middleware가 검색 엔진 크롤러를 차단하지 않음
성능 최적화
Sitemap 캐시 전략
API 라우트로 Sitemap을 생성한다면 캐시를 추가해야 합니다.
// app/sitemap.xml/route.ts
export const revalidate = 3600 // 한 시간 캐시
export async function GET() {
// ...Sitemap 생성
return new NextResponse(sitemap, {
headers: {
'Content-Type': 'application/xml',
'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate',
},
})
}
증분 업데이트와 전체 재빌드 비교
- 소규모 웹사이트(< 1000페이지): 매번 전체 재빌드합니다. 간단하고 편리합니다.
- 중규모 웹사이트(1000~10000페이지): ISR을 사용해 매시간 또는 매일 재검증합니다.
- 대규모 웹사이트(> 10000페이지): 여러 Sitemap으로 나누고 변경된 부분만 증분 업데이트합니다.
CDN 설정
Cloudflare 같은 CDN을 사용한다면 Sitemap과 robots.txt도 캐시되는지 확인합니다.
- 적절한 Cache-Control 헤더를 설정합니다.
- CDN이 XML과 TXT 파일을 캐시하도록 허용합니다.
- 콘텐츠를 업데이트한 뒤 CDN 캐시를 삭제합니다.
마무리
전체 설정 절차를 다시 정리해 보겠습니다.
-
Sitemap 생성 방법 선택:
- 소규모 프로젝트는 App Router 기본 방식을 사용합니다.
- 중대형 프로젝트는 next-sitemap을 사용합니다.
- 매우 세밀한 사용자 정의가 필요하면 API 라우트를 사용합니다.
-
robots.txt 설정:
- 색인할 필요가 없는 디렉터리를 차단합니다.
- Sitemap 참조를 추가합니다.
- 개발 환경에서는 크롤링을 차단합니다.
-
Google Search Console에 제출:
- 웹사이트 소유권을 확인합니다.
- Sitemap을 제출합니다.
- 색인 상태를 모니터링합니다.
-
자주 발생하는 문제 해결:
- Middleware가 크롤러를 차단하지 않도록 합니다.
- XML 형식이 올바른지 확인합니다.
- 캐시 문제는 타임스탬프 매개변수로 해결합니다.
솔직히 Sitemap과 robots.txt 설정은 아주 어려운 기술은 아니지만 세부 사항이 많아 조금만 부주의해도 문제가 발생합니다. 저도 처음 설정했을 때 수많은 시행착오를 겪었고, robots.txt가 사이트 전체를 차단한 지 한 달이 지나서야 문제를 발견했습니다.
이제 새 프로젝트를 배포할 때마다 이 체크리스트를 따라 확인하고 있어 거의 문제가 생기지 않습니다. 이 글이 시행착오를 줄이고 여러분의 웹사이트가 검색 엔진에 빠르게 색인되는 데 도움이 되길 바랍니다.
다른 문제를 겪었거나 공유하고 싶은 경험이 있다면 댓글로 알려 주세요!
Next.js Sitemap과 robots.txt 전체 설정 절차
파일 생성부터 Google Search Console 제출까지 이어지는 전체 SEO 설정 절차
⏱️ Estimated time: 1 hr
- 1
Step 1: 동적 Sitemap 만들기
app/sitemap.ts 파일을 만듭니다.
• sitemap 배열을 반환하는 default 함수를 내보냅니다.
• 각 항목에 url, lastModified, changeFrequency, priority를 넣습니다.
• async 함수로 동적 데이터를 가져올 수 있습니다.
예시:
export default async function sitemap() {
const posts = await getPosts()
return [
{
url: 'https://example.com',
lastModified: new Date(),
changeFrequency: 'yearly',
priority: 1,
},
...posts.map(post => ({
url: `https://example.com/posts/${post.id}`,
lastModified: post.updatedAt,
changeFrequency: 'weekly',
priority: 0.8,
}))
]
} - 2
Step 2: robots.txt 만들기
app/robots.ts 파일을 만듭니다.
• robots 설정을 반환하는 default 함수를 내보냅니다.
• 허용하거나 차단할 크롤러를 설정합니다.
• sitemap 경로를 지정합니다.
예시:
export default function robots() {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api/', '/admin/'],
},
sitemap: 'https://example.com/sitemap.xml',
}
}
주의: 사이트 전체를 실수로 disallow하지 마세요. - 3
Step 3: 생성된 파일 확인하기
생성된 파일을 확인합니다.
• /sitemap.xml에 접속해 sitemap을 확인합니다.
• /robots.txt에 접속해 robots 설정을 확인합니다.
• sitemap에 모든 페이지가 포함되었는지 확인합니다.
• robots.txt가 사이트를 잘못 차단하지 않았는지 확인합니다.
검증 도구:
• Google Search Console
• 온라인 XML 검증기
• 브라우저에서 직접 접속해 형식 확인 - 4
Step 4: Google Search Console에 제출하기
절차:
1. Google Search Console 계정을 만듭니다.
2. 웹사이트 속성을 추가하고 소유권을 확인합니다.
3. sitemap.xml URL을 제출합니다.
4. robots.txt가 크롤링을 허용하는지 확인합니다.
확인 방법:
• HTML 파일 업로드
• HTML 태그 확인
• DNS 레코드 확인
• Google Analytics 확인 - 5
Step 5: 동적 페이지 처리하기
동적 라우트 처리:
• sitemap.ts에서 모든 동적 페이지 데이터를 가져옵니다.
• 동적 페이지마다 URL을 생성합니다.
• 올바른 lastModified 시간을 설정합니다.
예시:
const products = await getAllProducts()
const productUrls = products.map(product => ({
url: `https://example.com/products/${product.id}`,
lastModified: product.updatedAt,
changeFrequency: 'weekly' as const,
priority: 0.8,
})) - 6
Step 6: 모니터링하고 최적화하기
지속적으로 모니터링합니다.
• Google Search Console을 정기적으로 확인합니다.
• sitemap 제출 상태를 확인합니다.
• robots.txt가 크롤링에 영향을 주는지 확인합니다.
• 색인 상태를 모니터링합니다.
최적화 제안:
• sitemap을 제때 업데이트하고 새 페이지를 빠르게 추가합니다.
• 적절한 changeFrequency를 설정합니다.
• 중요한 페이지에는 높은 priority를 설정합니다.
• robots.txt 설정을 정기적으로 확인합니다.
FAQ
Sitemap은 반드시 필요한가요?
동적 라우트용 Sitemap은 어떻게 생성하나요?
robots.txt를 잘못 설정하면 어떤 문제가 생기나요?
Sitemap을 제출한 뒤 Google이 크롤링하기까지 얼마나 걸리나요?
Sitemap이 올바른지 어떻게 확인하나요?
robots.txt로 특정 크롤러를 차단할 수 있나요?
Sitemap에 모든 페이지를 넣어야 하나요?
4분 읽기 · 게시일: 2025년 12월 20일 · 수정일: 2026년 9월 4일
Next.js 완전 가이드
검색으로 들어왔다면 같은 시리즈의 이전 글이나 다음 글로 이동하는 것이 가장 빠릅니다.
이전
Next.js Core Web Vitals 최적화 실전: LCP/FCP/CLS 완벽 가이드
Next.js의 LCP, FCP, CLS 핵심 지표를 최적화해 Lighthouse 점수를 90점 이상으로 높이는 완벽 가이드입니다. 10개 이상의 코드 예제와 흔한 함정, 실전 팁을 담았습니다.
45편 중 24편
다음
Next.js TypeScript 고급 설정: tsconfig 최적화와 타입 안전성 실전 가이드
Next.js TypeScript 설정 최적화 방법을 깊이 있게 다룹니다. tsconfig 엄격 모드 설정, 타입 안전 라우팅 구현, 환경 변수 타입 정의를 통해 any 타입을 없애고 개발 경험을 개선하는 방법을 알아봅니다.
45편 중 26편



댓글
GitHub로 로그인하여 댓글을 남기세요