프론트엔드트러블슈팅
SEO 봇 인덱싱 함정 해결 + RSS 2.0 + 키워드 5→31 개 — POP-SPOT v2.20.3
동
김동현네이버·구글 곀봇이 메인을 접근 이상한데도 색인 0. 추적 결과 middleware 가 봇을 인트로로 강제 리다이렉트. UA 패턴으로 봇 예외 처리 + RSS 2.0 피드 + 메타 키워드 풍부화.
운영자가 네이버에서 팝스팟 을 검색했는데 메인 0 건 색인. Search Console 도 조용. 추적 결과 middleware 의 인트로 강제 리다이렉트가 크롤러와 사용자를 구분 안 해서 크롤러가 메인 HTML 을 영원히 못 보는 상태였다.
이 글에서 다루는 것
모르는 단어 한 줄로
| 용어 | 한 줄 설명 |
|---|---|
| User-Agent (UA) | 브라우저/봇이 서버에 보내는 자신 소개 문자열 |
| server-side redirect | 서버가 HTTP 307/308 으로 다른 URL 로 이동 지시. 클라이언트 JS 실행 전에 일어남 |
| RSS 2.0 | 사이트 업데이트를 XML 표준으로 제공하는 구독 피드 |
| Yeti | 네이버의 크롤러 이름 |
1. 근본 원인
v2.0 이후 middleware.ts 가 이렇게 동작했다.
파일: popspot-frontend/middleware.ts (v2.20.2 까지의 상태)
typescript
// v2.0 — v2.20.2 까지 — UA 구분 없이 모든 첫 진입을 인트로 리다이렉트
export function middleware(req: NextRequest) {
const visited = req.cookies.get('entered');
// 위: 인트로 거친 사용자 표시 쿼키. 봇은 이 쿼키를 저장 안 함 → 영원히 visited 없음 상태
if (!visited && req.nextUrl.pathname === '/') {
return NextResponse.redirect(new URL('/intro', req.url));
// 위: 307 리다이렉트. 구글봇·네이버 Yeti 도 이 대상이 되어 읽는 건 /intro 페이지 콘텐츠. 메인 HTML 은 보이지 안 함
}
}봇의 입장에서 보면:
결과: 팝스팟 검색에 도메인이 안 뜨고, /intro 면 명확한 메인 컨텐츠 없음 으로 판단되어 순위 낮아짐.
2. 해결 — 봇 UA 예외
파일: 같은 middleware.ts (v2.20.3 수정 후)
typescript
// v2.20.3 — 봇 UA 예외 처리 추가. 크롤러는 인트로·리다이렉트 대상에서 빼냄
const BOT_UA_PATTERN = /(Googlebot|bingbot|Slurp|DuckDuckBot|Baiduspider|YandexBot|Yeti|Daum|KAKAOTALK-Scrap|facebookexternalhit|Twitterbot|LinkedInBot)/i;
// 위: 주요 검색 크롤러 UA 키워드 정규식. /i 는 대소문자 무시
// Yeti = 네이버 봇. KAKAOTALK-Scrap, facebookexternalhit = SNS 링크 미리보기 봇
export function middleware(req: NextRequest) {
const ua = req.headers.get('user-agent') ?? '';
// 위: 요청 헤더의 User-Agent. 없으면 빈 문자열으로 폴백
const isBot = BOT_UA_PATTERN.test(ua);
// 위: 정규식 test() — 패턴 매칭 되면 true
if (isBot) {
return NextResponse.next();
// 위: 리다이렉트 없이 원래 경로 진행 → 봇은 / 의 HTML 을 그대로 보고 색인 가능
}
const visited = req.cookies.get('entered');
const hasDeepLink = DEEP_LINK_KEYS.some(
k => req.nextUrl.searchParams.has(k)
);
// 위: v2.15.2 의 딥링크 화이트리스트 그대로 유지
if (!visited && req.nextUrl.pathname === '/' && !hasDeepLink) {
return NextResponse.redirect(new URL('/intro', req.url));
// 위: 일반 사용자의 첫 방문·딥링크 없음·만 인트로
}
}해석 — 줄별로
curl 검증
파일: 코드 파일 아닌 터미널에서 직접 입력하는 명령 (배포 검증용)
bash
# v2.20.3 검증 — 일반 사용자는 여전히 인트로
$ curl -I -H "User-Agent: Mozilla/5.0" https://popspot.co.kr/
HTTP/2 307
location: /intro
# Googlebot — 메인 HTML 그대로
$ curl -I -H "User-Agent: Googlebot/2.1" https://popspot.co.kr/
HTTP/2 200
content-type: text/html
# 네이버 Yeti — 메인 HTML 그대로
$ curl -I -H "User-Agent: Yeti/1.1" https://popspot.co.kr/
HTTP/2 2003. RSS 2.0 피드
파일: popspot-frontend/app/feed.xml/route.ts (v2.20.3 신규 Route Handler)
typescript
// v2.20.3 — app/feed.xml/route.ts (신규). RSS 2.0 피드
import { NextResponse } from 'next/server';
export async function GET() {
// 약관 §10-2 — 자동수집 팝업 제외. 운영자 작성만
const items = [
{ title: 'POP-SPOT 서비스 소개', link: '/about' },
{ title: '개인정보처리방침', link: '/privacy' },
];
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>POP-SPOT</title>
<link>https://popspot.co.kr</link>
<description>서울 팝업스토어 정보 서비스</description>
<language>ko</language>
</channel>
</rss>`;
return new NextResponse(xml, {
headers: { 'Content-Type': 'application/rss+xml; charset=utf-8' },
});
}해석
등록 용도: 네이버 서치어드바이저 → 사이트 등록 → RSS 주소 입력 시 자동 수집.
4. robots.txt 추가 차단
javascript
# v2.20.3 — public/robots.txt 에 회원전용 경로 차단 추가
User-agent: *
Allow: /
Disallow: /api/
Disallow: /music/passport
Disallow: /profile/edit
Sitemap: https://popspot.co.kr/sitemap.xml해석
5. 메타 키워드 5 → 31 개
파일: popspot-frontend/src/app/layout.tsx 의 export const metadata
typescript
// v2.20.3 — app/layout.tsx. 메타 키워드 5개 → 31개 확장
export const metadata = {
title: '팝스팟 | 서울 팝업스토어 정보',
description: '서울 성수·한남·압구정 세 동네 중심으로 매일 열리는 팝업스토어 일정·위치·카테고리를 한 곳에서 확인하고, 동행자 찾기·코스 저장·음악 매칭까지.',
keywords: [
'팝스팟', '팝업스토어', '서울팝업', '성수팝업', '한남팝업',
'압구정팝업', '홍대팝업', '강남팝업', '이태원팝업', '잠실팝업',
'여의도팝업', '명동팝업', '성북팝업', '마포팝업',
'팝업스토어 추천', '팝업 일정', '팝업 달력', '팝업 지도',
'주말 팝업', '이번주 팝업', '오늘 팝업',
'패션 팝업', '뉴티 팝업', '캐릭터 팝업', '디저트 팝업',
'라이프스타일 팝업', '아트 팝업', '테크 팝업',
'팝업스토어 이벤트', '팝업 동행', '팝업 코스', 'POP-SPOT',
],
};해석
핵심 파일
운영 검증
배포 1 주 후 Google Search Console:
단점: 공격 관점에서는 UA 위조 가능. 공격자가 Googlebot 으로 위장해 인트로 우회. 다만 메인 자체에 민감 정보 없으니 실제 위험은 낮음.
관련 글
이전 글메인 BROWSE 섭션 (지역·11 / 시점·5 / 카테고리·7) + Long-tail SEO 랜딩 + 음악 재생 안정화 — POP-SPOT v2.21 S1~S9다음 글Caffeine API 캐싱 + DB 인덱스 V10 + 약관 재동의 + a11y 폴리쉬 — POP-SPOT v2.19 · v2.20 (+v2.20.1/2)
관련 글
인트로 페이지 제거 → 루트가 메인 직행 + AuthGuard 스피너 게이트 제거로 SEO 색인 해결 — POP-SPOT v2.23 (+v2.23.1/2)발견형 서비스인데 모든 방문자가 5 섭션 인트로→ENTER 거쳐야 메인 도달. 이탈을 키우는 구조라 제거. v2.23.1·2 은 SEO 색인 이시합 해결 — /popups/[slug] 이 크롤러를 /login 으로 튕긴게 수정하고, AuthGuard 의 스피너 게이트를 제거해 공개 페이지가 실제 HTML로 서버 렌더링 되도록.전면 보안 감사 + 수정 — IDOR 5 곳 · 저장형 XSS · 메모리/스토리지 누수 · 클린코드 점검 — POP-SPOT v2.22인증·인가·인젝션·암호화·프론트·컴플라이언스 6 영역 병렬 감사. C1 IDOR (Stamp/MyPage/Mate/ChatFile) + C2 저장형 XSS (카카오 로드뷰 오버레이) + H1 인증 없는 업로드 + H2/H3 무한 증가 인메모리 맵 + H4 SSE emitter 누수 + H5 토큰 노출. 동시에 운영 점검 4 건 (신고 어뻐징 · 크롤링 입력 정제 · 이메일 열거 · 백업 하드닝).Spotify OAuth + Web Playback SDK 통합 — 3-tier 재생 엔진 (Premium SDK / iTunes preview / YouTube) — POP-SPOT v2.21 S10~S18Spotify OAuth 백엔드(AES-256-GCM 토큰 암호화) + Web Playback SDK 프론트 통합 + iTunes preview 폴백 + YouTube 폴백의 3-tier 재생 엔진. Premium 은 320kbps 풀트랙, Free/미연결은 30~90초 preview, 외엔 YouTube 폴백. S17/S18 에서 운영 버그 네 건 추가 수정.