logo

DowanKim

10. 내 사이트가 구글 검색창에 뜨게 하고 싶다.

2025년 11월 28일

포트폴리오 사이트

image.png

image.png

seo를 위해서 리액트가 아닌 넥스트를 사용하기로 정했었습니다. 하지만 그냥 넥스트로 만든다고 해서 구글 검색에 바로 나타나는건 아니었습니다.. 아래는 seo를 챙기기 위해 노력했던 과정, 결과적으로 위 사진의 결과가 나타나기 까지의 과정입니다.

트러블슈팅 16: Next.js + Vercel 배포 후 구글 검색에 노출되지 않은 문제 - SEO를 챙기기까지의 과정

1. 문제 상황

GitHub Pages로 배포한 정적 포트폴리오는 구글에서 바로 검색되었습니다. Next.js + Vercel로 새로 만든 사이트는 검색해도 결과에 나타나지 않았습니다.

  • 사이트는 정상 작동
  • 도메인도 연결됨 (dowankim.site) 돈도 없는데 멋있고싶어서 도메인 샀음..
  • 하지만 구글 검색 결과에 없음
  • seo를 위해 next를 사용한건데..

2. 원인 분석

2.1 기존 사이트가 검색된 이유

GitHub Pages 버전:

  • 오래 사용하며 크롤링과 백링크가 쌓임
  • 구글이 이미 사이트를 알고 있음
  • 자동으로 크롤링됨

2.2 새 사이트가 검색되지 않은 이유

새 도메인 (dowankim.site):

  • 구글에 "이 도메인을 인덱싱해도 된다"는 신호를 주지 않음
  • Search Console에 소유권 인증이 없으면 구글봇이 적극적으로 방문하지 않음
  • 사이트맵이나 메타데이터가 없으면 크롤링이 느림

3. 해결 과정

3.1 1단계: Search Console 도메인 속성 생성

Google Search Console에 접속하여 새 도메인을 등록했습니다.

과정:

  1. Search Console 접속
  2. "속성 추가" 클릭
  3. "도메인(권장)" 옵션 선택
  4. dowankim.site 입력
  5. TXT 레코드 인증 필요

3.2 2단계: 잘못된 시도 - 가비아 DNS에 TXT 입력

문제의 시작:

  • 루트 도메인 네임서버를 Vercel로 변경한 상태였음
  • 습관적으로 가비아 DNS 화면에 google-site-verification=... TXT 레코드를 추가
  • Search Console에서 계속 "소유권 확인 실패"

왜 실패했나?

  • 가비아는 도메인 등록처로만 사용 중
  • 실제 DNS는 ns1.vercel-dns.com이 관리 (ns2도 있긴함)
  • 가비아 DNS에 추가해도 Vercel DNS에 반영되지 않음

3.3 3단계: 원인 파악

문제 인식:

  • 네임서버가 Vercel이면 TXT 레코드도 Vercel에서 관리해야 함
  • Vercel Dashboard의 DNS Records에 추가해야 함

3.4 4단계: 정상 시도 - Vercel DNS에 TXT 추가

올바른 방법:

  1. Vercel Dashboard 접속
  2. Domains > dowankim.site > DNS Records 이동
  3. 새 레코드 추가:
    • Type: TXT
    • Name: @ (루트 도메인)
    • Value: google-site-verification=... (Search Console에서 제공한 값)
    • TTL: 기본값 60
  4. 5~10분 대기 (DNS 전파 시간)
  5. Search Console에서 "확인" 재시도
  6. 소유권 인증 성공

3.5 5단계: 크롤링 안내 파일 준비

sitemap.ts 작성

import type { MetadataRoute } from 'next' const baseUrl = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dowankim.site' export default function sitemap(): MetadataRoute.Sitemap { const now = new Date() return [ { url: `${baseUrl}/`, lastModified: now, changeFrequency: 'weekly', priority: 1, }, { url: `${baseUrl}/blog`, lastModified: now, changeFrequency: 'weekly', priority: 0.8, }, { url: `${baseUrl}/projects`, lastModified: now, changeFrequency: 'monthly', priority: 0.7, }, { url: `${baseUrl}/contact`, lastModified: now, changeFrequency: 'monthly', priority: 0.6, }, ] }

동작:

  • Next.js가 /sitemap.xml 경로를 자동 생성
  • 사이트 구조를 검색엔진에 알림
  • priority, changeFrequency로 중요도와 업데이트 빈도 표시

robots.ts 작성

import type { MetadataRoute } from 'next' const baseUrl = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dowankim.site' export default function robots(): MetadataRoute.Robots { return { rules: [ { userAgent: '*', allow: '/', }, ], sitemap: `${baseUrl}/sitemap.xml`, host: baseUrl, } }

동작:

  • Next.js가 /robots.txt 경로를 자동 생성
  • 모든 크롤러에 모든 페이지 접근 허용
  • 사이트맵 위치 명시

3.6 6단계: Search Console에 사이트맵 제출

과정:

  1. Search Console 왼쪽 메뉴에서 "Sitemaps" 클릭
  2. https://dowankim.site/sitemap.xml 입력
  3. "제출" 클릭
  4. "성공" 확인

효과:

  • 구글이 사이트 구조를 이해
  • 크롤링 우선순위 설정 가능
  • 인덱싱 속도 향상

3.7 7단계: 메타데이터와 OG 태그 정비

왜 필요한가?

문제:

  • 이전 GitHub Pages용 메타 태그가 그대로
  • 새 도메인 정보가 검색/SNS에 전달되지 않음
  • og:image, description이 오래된 URL을 가리켜 공유 시 잘못된 썸네일 노출

해결

  1. app/layout.tsx - 전역 메타데이터 설정
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://dowankim.site' const defaultOgImage = `${siteUrl}/images/og.webp` export const metadata: Metadata = { metadataBase: new URL(siteUrl), title: { default: 'Dowan Kim Portfolio', template: '%s | Dowan Kim', }, description: '프론트엔드 개발자 김도완의 포트폴리오와 블로그', authors: [{ name: 'Dowan Kim' }], keywords: [ 'Dowan Kim', '김도완', 'Frontend', 'Next.js', 'React', 'Portfolio', 'Blog', ], openGraph: { title: 'Dowan Kim Portfolio', type: 'website', url: siteUrl, siteName: 'Dowan Kim Portfolio', description: '프론트엔드 개발자 김도완의 대표 프로젝트와 블로그 글', images: [ { url: defaultOgImage, width: 1200, height: 630, alt: 'Dowan Kim Portfolio', }, ], }, twitter: { card: 'summary_large_image', title: 'Dowan Kim Portfolio', description: '프론트엔드 개발자 김도완의 대표 프로젝트와 블로그 글', images: [defaultOgImage], }, }

핵심 포인트:

  • metadataBase: 상대 경로를 절대 경로로 변환하는 기준 URL
  • title.template: 페이지별 제목 템플릿 (예: "블로그 글 제목 | Dowan Kim")
  • NEXT_PUBLIC_SITE_URL 환경변수: 로컬/배포 환경에서 동일한 코드 사용
  1. app/blog/[tag]/[slug]/page.tsx - 동적 메타데이터 생성
export async function generateMetadata({ params }: PageProps): Promise<Metadata> { const decodedSlug = decodeURIComponent(params.slug) const decodedTag = decodeURIComponent(params.tag) const post = await getPostBySlug(decodedSlug) if (!post) { return { title: '게시글을 찾을 수 없습니다', description: '요청한 블로그 글이 존재하지 않습니다.', } } // 마크다운을 텍스트로 정제 const plain = stripMarkdown(post.content) const description = plain.length > 160 ? `${plain.slice(0, 157).trim()}...` : plain || post.title // 이미지 URL을 절대 경로로 변환 const primaryImage = post.images?.[0] ? ensureAbsoluteUrl(post.images[0]) : defaultOgImage const canonicalUrl = `${siteUrl}/blog/${encodeURIComponent(decodedTag)}/${encodeURIComponent(decodedSlug)}` const createdAtDate = toDate(post.createdAt as Date | { toDate: () => Date }) return { title: post.title, description, alternates: { canonical: canonicalUrl, }, openGraph: { type: 'article', title: post.title, description, url: canonicalUrl, siteName: 'Dowan Kim Portfolio', images: [ { url: primaryImage, width: 1200, height: 630, alt: post.title, }, ], tags: post.tags, publishedTime: createdAtDate.toISOString(), }, twitter: { card: 'summary_large_image', title: post.title, description, images: [primaryImage], }, } }

핵심 기능:

  • stripMarkdown: 마크다운을 텍스트로 변환하여 description 생성
  • ensureAbsoluteUrl: 상대 경로 이미지를 절대 경로로 변환
  • canonical: 중복 콘텐츠 방지
  • openGraph.article: 블로그 글 정보 (태그, 발행일)

효과

  • 검색엔진/크롤러가 최신 도메인과 페이지 정보를 정확히 인식
  • SNS 공유 시 올바른 제목·설명·썸네일 노출로 클릭률 개선 기대

4. 메타데이터와 OG 태그 기본 개념

4.1 메타데이터란?

HTML <head> 내에서 페이지를 설명하는 정보 세트입니다.

주요 요소:

  • <title>: 페이지 제목
  • <meta name="description">: 페이지 설명
  • <link rel="canonical">: 원본 URL
  • <meta name="robots">: 크롤링 지시

역할:

  • 검색엔진이 색인할 때 제목·요약·원본 URL을 이해하는 데 사용
  • 검색 결과에 표시되는 정보 제공

4.2 OG(Open Graph)란?

Facebook이 만든 공유 미리보기 표준으로, 대부분의 SNS가 참고합니다.

주요 태그:

  • og:title: 공유 시 제목
  • og:description: 공유 시 설명
  • og:image: 공유 시 썸네일 이미지
  • og:url: 공유 시 URL
  • article:published_time: 발행 시간 (블로그 글)

역할:

  • SNS 공유 시 미리보기 표시
  • 없으면 잘못된 썸네일/텍스트가 노출되어 클릭률 저하

4.3 적용

  1. app/layout.tsx에서 전역 기본값 정의

    • 제목 템플릿, 설명, OG/Twitter 이미지
    • 모든 페이지에 공통 적용
  2. generateMetadata로 페이지별 맞춤 메타 정보 생성

    • 특히 블로그 글은 동적으로 생성
    • 각 글마다 고유한 메타데이터
  3. 환경 변수 활용

    • .env.local 및 Vercel 환경 변수에 NEXT_PUBLIC_SITE_URL 지정
    • 로컬/배포 환경에서 동일한 코드 사용

5. 주의사항

5.1 기억할 것

  1. 메타데이터 수정 후 재시작 필요

    • 로컬 개발 서버나 배포를 다시 실행해야 적용됨
    • Next.js는 빌드 시 메타데이터를 생성
  2. 새 페이지 생성 시 메타데이터 고려

    • generateMetadata 또는 metadata export를 함께 작성
    • SEO를 처음부터 고려
  3. 이미지 경로는 절대 경로 사용

    • SNS는 절대 경로만 인식
    • metadataBase를 설정하면 상대 경로도 자동 변환

5.2 추가

  1. 인덱싱은 시간이 걸림

    • 인덱싱 요청 후 실제 검색 노출까지 며칠~수주 소요
    • 인내심 필요
  2. 외부 링크로 크롤링 빈도 증가

    • Naver 블로그, GitHub README 등에 새 도메인 링크 추가
    • 백링크가 많을수록 크롤링 빈도 증가
  3. 정기적인 확인

    • noindex, canonical 설정이 올바른지 확인
    • robots.txt가 차단하고 있지 않은지 확인
    • Search Console에서 인덱싱 상태 모니터링

6. 결과

  1. DNS TXT 인증 완료

    • Search Console에서 인덱싱 요청 가능
    • 구글 크롤러가 새 도메인 방문 시작
  2. 사이트맵과 robots.txt 준비

    • 사이트 구조를 검색엔진에 명확히 전달
    • 크롤링 효율 향상
  3. 메타데이터 정비

    • 검색 결과에 올바른 정보 표시
    • SNS 공유 시 올바른 미리보기 표시
  4. GitHub Pages 대비 노출이 느린 이유 이해

    • 새 도메인, 링크 부족 등이 원인
    • 인덱싱을 위한 최소 절차 완료

7. 마무리

  1. Search Console 소유권 인증: DNS TXT 레코드 추가 (Vercel DNS에)
  2. 사이트맵과 robots.txt: 크롤링 안내 파일 준비
  3. 메타데이터 정비: 전역 설정과 동적 생성
  4. OG 태그 설정: SNS 공유 최적화

이 과정을 통해:

  • 구글 검색에 노출될 수 있는 기반 마련
  • SNS 공유 시 올바른 정보 표시
  • 검색엔진이 사이트를 정확히 이해

SEO는 한 번에 끝나지 않습니다. 지속적인 모니터링과 개선이 필요할 것 같습니다.

image.png