robots.txt 및 sitemap.xml 설정
robots.ts로 크롤링 허용 범위를 정하고 sitemap.ts로 색인할 URL과 갱신 정보를 검색 엔진에 제공합니다.
SEO를 최적화할 때 robots.txt와 sitemap.xml은 검색 엔진 크롤러에 주는 핵심 지침입니다.
이 두 파일은 웹사이트 노출성뿐 아니라, 검색 엔진이 사이트 구조를 효율적으로 이해하고 색인하는 데 큰 영향을 줍니다.
Next.js는 이 파일을 효과적으로 관리할 수 있는 방법을 제공해 SEO 친화적인 애플리케이션 구축을 돕습니다.
이 절에서는 robots.txt와 sitemap.xml의 역할과 중요성, Next.js App Router에서 이 파일들을 설정하는 방법, 그리고 SEO를 위한 최적화 전략에 대해 상세히 알아보겠습니다.
robots.txt
robots.txt 파일은 웹사이트의 루트 디렉터리에 위치하는 텍스트 파일입니다.
이 파일은 검색 엔진 크롤러(웹 로봇)에게 웹사이트 내에서 어떤 URL에 접근할 수 있고 없는지를 알려 주는 역할을 합니다.
이는 검색 엔진이 어떤 URL을 크롤링할지 범위를 조절하고, 불필요한 요청으로 인한 서버 부하를 줄이는 데 사용됩니다.
주요 목적- 크롤링 범위 조절: 검색 엔진에 특정 디렉터리나 파일을 크롤링하지 말라고 요청합니다.
- 서버 부하 감소: 불필요한 페이지에 대한 크롤링 요청을 줄여 서버 리소스 낭비를 방지합니다.
- 크롤링 우선순위 정리: 중복·필터 URL보다 대표 콘텐츠를 먼저 탐색하도록 범위를 정리합니다.
주의사항: robots.txt는 보안 메커니즘이 아닙니다.
이 파일은 단순히 크롤러에게 "이곳은 가지 말아달라"고 요청하는 것이며, 악의적인 봇이나 직접 URL을 입력하는 사용자를 막을 수는 없습니다.
민감한 정보는 서버 측 인증이나 다른 보안 조치로 보호해야 합니다.
robots.txt 파일 구조
robots.txt는 User-agent와 Disallow, Allow, Sitemap 지시어로 구성됩니다.
User-agent: 특정 검색 엔진 봇을 지정합니다.*는 모든 봇을 의미합니다.- 예시:
User-agent: *(모든 봇),User-agent: Googlebot(구글 봇),User-agent: bingbot(빙 봇)
- 예시:
Disallow: 지정된 봇에 크롤링하지 말라고 요청할 경로를 지정합니다.- 예시:
Disallow: /admin/(admin 디렉터리 전체),Disallow: /private-page.html(특정 파일)
- 예시:
Allow:Disallow규칙 내에서 특정 서브 경로를 허용할 때 사용합니다.- 예시:
Disallow: /private/Allow: /private/public-page.html
- 예시:
Sitemap: 사이트맵 파일의 위치를 지정합니다. 이 지시어는 크롤러가 사이트맵을 쉽게 찾을 수 있도록 돕습니다.
App Router에서 robots.txt 설정
App Router에서는 정적 src/app/robots.txt와 동적으로 생성하는 src/app/robots.ts 중 하나를 선택합니다.
두 파일은 모두 /robots.txt를 만들기 때문에 한 프로젝트에서 함께 사용하지 않습니다.
이 교재에서는 환경이나 배포 주소에 따라 규칙을 확장하기 쉬운 src/app/robots.ts 한 가지 방식으로 통일합니다.
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*', // 모든 봇
allow: '/', // 모든 페이지 허용
disallow: ['/admin/', '/private/'], // 특정 경로 비허용
},
{
userAgent: 'Googlebot', // Googlebot만
allow: '/',
disallow: ['/admin/', '/private/', '/temp/'],
},
],
sitemap: 'https://docs-lab.dev/sitemap.xml', // 사이트맵 경로
};
}구체적인 Googlebot 그룹은 * 그룹의 규칙을 자동 상속하지 않습니다.
두 그룹 모두 막아야 하는 경로는 각 그룹에 명시합니다.
Next.js는 이 반환값을 /robots.txt로 제공합니다. 코드로 생성해도 기본적으로 캐시되며, 실제 서비스 주소는 배포 환경에 맞춥니다.
위 경로는 접두사 규칙입니다. 예를 들어 /admin/은 /admin/users에 적용되지만 /admin과는 다릅니다. /temp/는 Googlebot 그룹에만 추가되어 있습니다. 같은 봇에서 여러 경로 규칙이 맞으면 더 구체적인 규칙이 우선하며, 같은 길이로 충돌하면 Google은 허용 규칙을 선택합니다.
sitemap.xml: 검색 엔진을 위한 웹사이트 지도
sitemap.xml 파일은 웹사이트의 중요한 페이지 목록을 검색 엔진에 제공하는 XML 파일입니다.
이 파일은 검색 엔진이 웹사이트의 콘텐츠를 효율적으로 발견하고 크롤링하며 색인하도록 돕습니다.
특히 내부 링크가 적거나 깊이 숨겨진 페이지, 동적으로 생성되는 페이지처럼 검색 엔진이 자체적으로 발견하기 어려운 페이지를 노출하는 데 매우 중요합니다.
주요 목적- 크롤링 효율성 증대: 검색 엔진이 웹사이트의 모든 중요한 URL을 빠르고 효율적으로 찾을 수 있도록 합니다.
- 발견 보조: 내부 링크만으로 찾기 어려운 페이지를 알려 줍니다. 제출은 힌트이며 모든 URL의 수집·색인을 보장하지 않습니다.
- 추가 정보 제공: 각 URL에 대한 최종 수정일, 변경 빈도, 상대적 우선순위 등 검색 엔진에게 유용한 추가 정보를 제공할 수 있습니다.
sitemap.xml 파일 구조
sitemap.xml은 <urlset> 루트 요소 아래에 각 URL에 대한 <url> 요소를 포함합니다.
각 <url> 요소는 다음을 포함할 수 있습니다.
<loc>: (필수) 페이지의 전체 URL (절대 경로).<lastmod>: (선택) 페이지가 최종 수정된 날짜.<changefreq>: (선택) 페이지의 내용이 변경되는 빈도 (always, hourly, daily, weekly, monthly, yearly, never).<priority>: (선택) 웹사이트 내에서 이 URL의 상대적 중요도 (0.0 ~ 1.0). 기본값은 0.5.
App Router에서 sitemap.xml 설정
Next.js App Router는 src/app/sitemap.ts 또는 .js 파일을 통해 sitemap.xml을 생성할 수 있도록 지원합니다.
src/app/sitemap.ts를 사용한 사이트맵 생성
import { MetadataRoute } from 'next';
// 가상의 블로그 게시물 데이터
interface Post {
slug: string;
updatedAt: string;
}
async function getPosts(): Promise<Post[]> {
// 실제로는 여기서 DB 또는 API를 호출하여 모든 게시물의 slug와 업데이트 날짜를 가져옵니다.
// 예시를 위해 더미 데이터를 반환합니다.
return [
{ slug: 'nextjs-seo-guide', updatedAt: '2024-06-20T10:00:00Z' },
{ slug: 'dynamic-metadata-tutorial', updatedAt: '2024-06-21T15:30:00Z' },
{ slug: 'image-optimization-tips', updatedAt: '2024-06-19T09:00:00Z' },
];
}
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://docs-lab.dev'; // 실제 웹사이트 URL로 변경
// 1. 정적인 페이지 목록
const staticRoutes: MetadataRoute.Sitemap = [
{
url: baseUrl,
changeFrequency: 'daily', // 매일 변경될 가능성
priority: 1, // 가장 높은 우선순위
},
{
url: `${baseUrl}/about`,
changeFrequency: 'monthly',
priority: 0.8,
},
{
url: `${baseUrl}/contact`,
changeFrequency: 'yearly',
priority: 0.7,
},
{
url: `${baseUrl}/blog`, // 블로그 목록 페이지
changeFrequency: 'weekly',
priority: 0.9,
}
];
// 2. 동적 블로그 게시물 목록
const posts = await getPosts();
const postRoutes: MetadataRoute.Sitemap = posts.map((post) => ({
url: `${baseUrl}/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'weekly', // 블로그 게시물은 보통 weekly 또는 monthly
priority: 0.6,
}));
// 3. 모든 URL 목록을 반환
return [...staticRoutes, ...postRoutes];
}실제 수정 시각을 모르는 정적 페이지는 lastModified를 생략합니다.
매번 new Date()를 반환하면 변경되지 않은 페이지까지 방금 수정된 것처럼 보이므로, CMS나 소스 데이터에 저장된 수정 시각이 있을 때만 제공합니다.
Next.js는 반환된 목록으로 /sitemap.xml을 제공하며, 이 특수 Route Handler도 기본 캐싱 대상입니다. 코드상 정적 URL 4개와 게시물 URL 3개를 반환하지만, URL을 나열하는 것만으로 페이지를 생성하거나 응답 상태를 검사하지는 않습니다.
이 절의 게시물 목록은 별도 모의 데이터입니다. 13장 2절과 함께 실행한다면 image-optimization-tips를 포함한 목록을 실제 게시물 저장소와 맞춥니다.
규칙을 적용해 보기
원 robots.ts를 바꾸지 않았을 때 Googlebot의 /temp/task, 다른 봇의 /temp/task, Googlebot의 /admin을 각각 판단하세요. 마지막 URL을 관리 화면으로 만들었다면 이 규칙만으로 일반 사용자의 접근을 막을 수 있을까요?
그룹·접두사·보호 조건 확인
Googlebot의 /temp/task는 비허용이고 * 그룹을 따르는 다른 봇에는 허용입니다. /admin은 끝의 슬래시가 없는 별도 경로이므로 원 /admin/ 규칙에는 맞지 않습니다. 실제 관리 화면은 앞 장처럼 서버의 인증·인가로 보호해야 하며, robots 지침을 접근 검사로 사용하지 않습니다.
사이트맵은 정적 네 개와 게시물 세 개를 합한 일곱 URL을 반환합니다. 이 중 image-optimization-tips는 사이트맵의 모의 목록에는 있지만 앞 게시물 페이지의 모의 목록에는 없습니다. 제출 전 두 저장소를 맞추고 실제 페이지 응답을 확인할 이유가 여기에 있습니다. priority: 1을 지정해도 존재하지 않는 페이지를 만들거나 색인을 보장하지 않습니다.
NEXT · 13.4
크롤링 규칙과 URL 발견 힌트는 서로 다른 역할입니다
본문 robots.ts의 실제 경로 규칙과 sitemap.ts가 반환하는 목록을 비교합니다. robots의 Sitemap 주소는 URL 목록 위치를 알려 줍니다.
robots.txt · 그룹별 접근 지침
| 경로 예시 | * 그룹 | Googlebot 그룹 |
|---|---|---|
/admin/users | 크롤링 비허용 | 크롤링 비허용 |
/admin | 허용 | 허용 |
/private/a | 크롤링 비허용 | 크롤링 비허용 |
/temp/task | 허용 | 크롤링 비허용 |
/admin/users- * 그룹: 크롤링 비허용 · Googlebot 그룹: 크롤링 비허용
/admin- * 그룹: 허용 · Googlebot 그룹: 허용
/private/a- * 그룹: 크롤링 비허용 · Googlebot 그룹: 크롤링 비허용
/temp/task- * 그룹: 허용 · Googlebot 그룹: 크롤링 비허용
/admin/ 접두사는 /admin과 다릅니다. Googlebot 그룹의 규칙은 별도로 선언하며 * 그룹을 자동 상속하지 않습니다.
sitemap.xml · 공개 URL 목록
- 정적 URL 4개
- 사이트 루트, /about, /contact, /blog를 반환합니다. 실제 수정 시각을 모르는 이 네 항목에는 lastModified가 없습니다.
- 게시물 URL 3개
- getPosts의 slug를 /blog/{slug} URL로 바꾸고 저장된 updatedAt을 lastModified로 전달합니다.
- 최종 URL 목록 7개
- 두 배열을 합쳐 /sitemap.xml로 제공합니다. URL 나열은 페이지 생성이나 정상 응답 확인을 수행하지 않습니다.
Disallow는 인증·인가나 검색 결과 제외를 보장하지 않습니다. noindex는 크롤러가 읽을 수 있어야 합니다. 사이트맵도 수집·색인 보장이 아니며 Google은 changefreq·priority를 무시합니다. 게시물 목록은 실제 저장소와 맞추어야 합니다.
SEO를 위한 최적화 전략
Next.js에서는 정적 파일과 App Router의 MetadataRoute 파일을 상황에 맞게 선택합니다.
robots.txt와sitemap.xml은 함께 사용:robots.txt는Disallow규칙을 통해 크롤링을 제어하고,sitemap.xml은 발견을 원하는 공개 대표 URL을 나열합니다. 두 파일은 상호 보완적입니다.robots.txt의Sitemap지시어 포함:robots.txt파일 내에Sitemap:지시어를 사용하여 사이트맵 파일의 전체 URL을 명시하는 것이 중요합니다. 이는 검색 엔진이 사이트맵을 쉽게 발견하도록 돕습니다.- Google Search Console에 등록: 사이트 속성을 확인하고
sitemap.xml을 제출합니다.robots.txt는 크롤러가 루트 경로에서 직접 읽으므로 실제 응답과 URL 검사 결과를 함께 확인합니다. - 크롤링과 인덱싱 구분:
robots.txt의Disallow는 크롤링을 제한할 뿐 검색 결과 제외를 보장하지 않습니다. 공개 페이지를 검색에서 제외하려면 크롤러가 읽을 수 있는noindex를 사용하고, 관리자 페이지와 개인정보는 인증·인가로 보호합니다. - 정확한
lastmod유지: 중요한 본문이 실제로 바뀐 시각만lastmod에 기록합니다.changefreq와priority는 Sitemap 프로토콜의 선택 필드지만 Google은 두 값을 무시하므로 크롤링 순위 조정 수단으로 사용하지 않습니다. - 동적 콘텐츠 관리: 블로그나 상품 URL은
sitemap.ts에서 저장소를 조회해 canonical URL을 열거합니다. 같은 목록을 빌드 시 미리 렌더링할 때만generateStaticParams와 데이터 조회 로직을 공유합니다. robots.txt변경 시 주의: 잘못된 규칙은 중요한 페이지의 크롤링을 막을 수 있습니다. 변경 후에는 실제 파일과 Search Console 보고서·URL 검사를 함께 확인합니다.- 파일 크기 제한:
sitemap.xml파일은 URL 50,000개 이하이면서 비압축 크기 50 MB 이하여야 합니다. 이보다 큰 웹사이트의 경우 여러 개의 사이트맵 파일로 분할하고,sitemap index file을 사용하여 관리해야 합니다.
robots.txt와 sitemap.xml은 웹사이트의 기술적 SEO 기반을 다지는 데 매우 중요한 요소입니다.
Next.js App Router의 내장 기능을 활용하면 이 두 파일을 효율적으로 관리하여 검색 엔진 가시성을 극대화하고 더 나은 검색 결과를 얻을 수 있습니다.