프론트엔드트러블슈팅

SEO 봇 인덱싱 함정 해결 + RSS 2.0 + 키워드 5→31 개 — POP-SPOT v2.20.3

김동현
··5분 읽기

네이버·구글 곀봇이 메인을 접근 이상한데도 색인 0. 추적 결과 middleware 가 봇을 인트로로 강제 리다이렉트. UA 패턴으로 봇 예외 처리 + RSS 2.0 피드 + 메타 키워드 풍부화.

운영자가 네이버에서 팝스팟 을 검색했는데 메인 0 건 색인. Search Console 도 조용. 추적 결과 middleware 의 인트로 강제 리다이렉트가 크롤러와 사용자를 구분 안 해서 크롤러가 메인 HTML 을 영원히 못 보는 상태였다.

이 글에서 다루는 것

  • 근본 원인 — server-side redirect 가 봇 UA 도 리다이렉트하면 봇은 메인 콘텐츠가 없다고 판단
  • User-Agent 패턴으로 봇 구별 + middleware 예외
  • RSS 2.0 피드 — 네이버 서치어드바이저 등록용
  • robots.txt 에 회원전용 페이지 차단
  • 메타 키워드 5 → 31 개 확장
  • 모르는 단어 한 줄로

    용어한 줄 설명
    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 은 보이지 안 함
      }
    }

    봇의 입장에서 보면:

  • Googlebot 이 GET /
  • 서버가 307 으로 /intro 로 리다이렉트 (entered 쿠키 없으니)
  • 봇이 인트로로 감. 인덱시는 /intro 가 됨
  • 메인 의 실제 콘텐츠는 봇이 영원히 못 봄
  • 결과: 팝스팟 검색에 도메인이 안 뜨고, /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));
        // 위: 일반 사용자의 첫 방문·딥링크 없음·만 인트로
      }
    }

    해석 — 줄별로

  • BOT_UA_PATTERN — 상위 검색·소셜 크롤러 UA 키워드 모음. i 플래그로 대소문자 무시
  • Yeti — 네이버 크롤러. 한 단어가 한국 검색 채널을 좌우함
  • KAKAOTALK-Scrap, facebookexternalhit — SNS 공유 링크 미리보기 봇. 이걸 통과시켜야 메인 OpenGraph 높아감
  • NextResponse.next() — 리다이렉트 없이 원래 경로대로 진행. 봇은 / 메인 HTML 제대로 봄
  • 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 200

    3. 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' },
      });
    }

    해석

  • app/feed.xml/route.ts — Next.js Route Handler 패턴. URL https://popspot.co.kr/feed.xml 에서 접근
  • 자동수집 팝업 제외 — 약관 §10-2 일관성. 자동수집 자료는 외부 제공 금지라 운영자 작성만
  • Content-Type: application/rss+xml — RSS 리더·구독기가 인식하는 표준 MIME
  • 등록 용도: 네이버 서치어드바이저 → 사이트 등록 → 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

    해석

  • /music/passport — 회원 전용 음악 기록·통계 페이지. 구글 검색 결과에 소용 없는 페이지
  • /profile/edit — 로그인 필수 페이지

  • 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',
      ],
    };

    해석

  • 5 개는 강한 단일 키워드. 31 개로 늘리면 검색 키워드 다양성 확보
  • 조심 — 키워드 수와 점수는 비례하지 않는다. 너무 많으면 원하는 키워드 점수 희석
  • description 은 키워드 나열이 아닌 자연어 문장. 클릭률에 직접적

  • 핵심 파일

  • popspot-frontend/middleware.ts (UA 패턴 추가)
  • popspot-frontend/app/feed.xml/route.ts (신규)
  • popspot-frontend/public/robots.txt
  • popspot-frontend/app/layout.tsx (keywords 확장)
  • 운영 검증

    배포 1 주 후 Google Search Console:

  • 메인 색인됨 상태로 전환
  • Yeti 가 메인 fetch 하는 횟수 증가
  • 팝스팟 검색 시 도메인 노출 시작
  • 단점: 공격 관점에서는 UA 위조 가능. 공격자가 Googlebot 으로 위장해 인트로 우회. 다만 메인 자체에 민감 정보 없으니 실제 위험은 낮음.


    관련 글

  • 이전 — v2.19/v2.20, 성능·컴플라이언스
  • 다음 — v2.21 S1~S9, 메인 BROWSE 섭션 + 음악 재생 안정화
  • 공유

    댓글