프로그래매틱 SEO로 253K 페이지를 색인한 방법: Next.js & Supabase

지난해 우리는 정말 불가능할 것 같았던 마일스톤을 달성했습니다: 동일한 스택으로 실행되는 3개의 프로덕션 사이트에 걸쳐 253,000개의 프로그래매틱 페이지가 색인되었습니다. 장난감 프로젝트가 아닙니다. 데모도 아닙니다. 실제 트래픽과 실제 수익을 가진 실제 사이트들입니다.

저는 Deluxe Astrology(91,000개 페이지), Not Another Sunday(137,000개 장소 목록), HostList(25,000개 호스팅 회사 프로필)를 정확히 어떻게 구축했는지 안내해드릴 것입니다. Supabase 쿼리, Next.js 페이지 아키텍처, 데이터 파이프라인은 물론, 가장 중요하게는 그 과정에서 무엇이 깨졌는지까지 포함합니다. 많은 것들이 깨졌거든요.

대부분의 온라인 프로그래매틱 SEO 콘텐츠는 누군가 문서를 훑어보고 끝낸 것처럼 읽힙니다. 이건 그렇지 않습니다. 우리는 1년 이상 이 페이지들을 배포하고 있으며, Google Search Console 그래프를 바라보고, 크롤 예산 제한에 대해 중얼거리고, 규모에서 실제로 작동하는 것이 무엇인지 천천히 파악해왔습니다.

목차

프로그래매틱 SEO: Next.js & Supabase로 253K 페이지를 색인한 방법

2025년 프로그래매틱 SEO가 실제로 의미하는 것

프로그래매틱 SEO는 템플릿을 사용하여 구조화된 데이터에서 규모에 맞게 페이지를 생성하는 것입니다. 그것이 한 문장 버전입니다. 현실은 훨씬 더 복잡합니다.

2025년의 Google 입장은 명확하지만 미묘합니다: 프로그래매틱이라는 이유만으로 프로그래매틱 콘텐츠에 페널티를 주지 않습니다. 얇고, 중복되거나, 도움이 되지 않을 때 페널티를 줍니다. $140M ARR에 기여하는 70,000개의 색인된 페이지를 가진 Zapier와 287,000개의 페이지가 거의 0에 가까운 색인을 받은 dev.to 사례 연구 사이의 차이는 한 가지로 귀결됩니다. 각 페이지가 사람이 검색창에 입력한 쿼리에 실제로 답하는지 여부입니다.

Ahrefs 데이터는 모든 웹 페이지의 96.55%가 0에 가까운 유기 트래픽을 받는다고 말합니다. 프로그래매틱 SEO는 동일한 콘텐츠의 변형만 생성하고 있다면 이 문제를 증폭시킵니다. 하지만 당신의 데이터가 진정으로 고유하고 당신의 템플릿이 서로 의미 있게 다른 페이지를 생성한다면 이를 놀랍게 해결할 수 있습니다.

우리가 사용하는 정신 모델은 이것입니다: 모든 프로그래매틱 페이지는 "이것을 북마크할까?" 테스트를 통과해야 합니다. Google에서 이 페이지에 도착했다면 머물렀을까요? 다른 곳에서는 찾을 수 없는 것을 찾을 수 있을까요? 답이 아니라면 게시하지 마세요.

3개 프로젝트: 프로덕션 수치

우리가 실제로 구축한 것과 숫자가 어떻게 나타나는지 설명하겠습니다.

프로젝트 페이지 콘텐츠 유형 지리적 범위 주요 지표
Deluxe Astrology 91,000 운세, 유명인 프로필, 천사 숫자, 우주 동전, 보석, 요가 자세, 이름 연구소, 점성술사 디렉토리 30개 언어 91K 색인 페이지
Not Another Sunday 137,000 NRI 점수, 사진, 지도가 있는 카페 & 로스터 장소 목록 미국, 영국, 일본 137K 고유 장소 페이지
HostList 25,000 HostScore 알고리즘이 있는 호스팅 회사 프로필 53개국 25K 색인 프로필
합계 253,000

Deluxe Astrology: 30개 언어에 걸친 91K 페이지

Deluxe Astrology는 단일 언어 운세 사이트로 시작했습니다. 규모는 콘텐츠 유형과 언어의 교집합에서 나왔습니다. 생각해보세요: 12개의 조디악 표시 × 365개의 일일 운세 × 30개 언어가 있다면, 하나의 콘텐츠 유형만으로도 이미 131,000개의 잠재적 페이지에 도달합니다. 우리는 선택적이었습니다 -- 모든 조합이 페이지를 얻는 것은 아닙니다 -- 하지만 점성술 콘텐츠의 조합론적 특성은 pSEO에 완벽합니다.

유명인 프로필 섹션만 해도 28,840개의 레코드를 가지고 있으며, 각각 Claude를 통해 출생도 분석, 성격 분석, 호환성 통찰력으로 보강되었습니다. 데이터 파이프라인에 대한 자세한 내용은 나중에 다룹니다.

Not Another Sunday: 137K 장소 목록

Not Another Sunday는 전문 커피 발견 플랫폼입니다. 모든 카페와 로스터는 독점적인 NRI(이웃 관련성 지수) 점수, 큐레이션된 사진, 내장 지도, 영업 시간, 리뷰가 있는 고유한 페이지를 받습니다. 우리는 여러 API, 사용자 생성 콘텐츠, 수동 큐레이션에서 데이터를 끌어옵니다.

핵심 통찰력: 두 개의 장소 페이지가 같지 않습니다. 두 개의 장소가 같지 않기 때문입니다. 템플릿은 일관성이 있지만 데이터가 매번 다르게 채웁니다. 라테 아트 경쟁과 4.8 NRI를 가진 시부야의 카페는 도매 전용 운영과 3.2 NRI를 가진 브루클린의 로스터와 완전히 다릅니다.

HostList: 53개국에 걸친 25K 호스팅 프로필

HostList는 세계 호스팅 회사 25,000개를 카탈로그하며, 각각은 가동 시간 데이터, 가격, 지원 응답성, 사용자 리뷰를 기반으로 하는 HostScore -- 우리의 알고리즘 등급을 가지고 있습니다. 53개국에 걸친 25,000개의 프로필, 각각 고유한 성능 데이터, 가격 테이블, 비교 위젯이 있습니다.

스택: Supabase, Next.js ISR, Vercel Edge

우리는 3개의 프로젝트 모두에 동일한 스택을 표준화했습니다. 여기서 각 부분이 중요한 이유입니다.

Supabase (PostgreSQL + pgvector): 우리의 전체 데이터 레이어는 Supabase에 살고 있습니다. PostgreSQL은 우리가 복잡한 쿼리에 필요한 관계형 구조를 제공합니다 (모든 12월에 태어난 같은 태양 표시를 가진 음악가인 궁수자리 유명인을 주세요), pgvector는 콘텐츠 전반에 걸쳐 의미론적 검색을 강화합니다. Supabase의 무료 계층은 500MB를 처리합니다; 우리는 8GB 데이터베이스가 있는 Pro에서 무제한 API 호출로 프로젝트당 $25/월에 있습니다.

ISR이 있는 Next.js (증분 정적 재생성): 모든 페이지는 빌드 시간에 또는 첫 번째 요청 시 정적으로 생성된 다음 일정에 따라 재검증됩니다. 이는 Google의 크롤러가 항상 빠르고 미리 렌더링된 HTML 페이지를 받는다는 의미입니다 -- 클라이언트 측 JavaScript를 기다리는 로딩 스피너가 아닙니다. 우리는 경로 생성을 위해 generateStaticParams와 함께 App Router를 사용합니다.

Vercel Edge: 한 곳에서 배포, CDN, 엣지 미들웨어가 모두 있습니다. Vercel의 Pro 플랜은 사용자당 $20/월로 1TB 대역폭을 제공하며, 253K 페이지의 트래픽을 편하게 처리합니다. Edge Middleware는 Deluxe Astrology의 30언어 설정을 위한 지리 라우팅을 처리합니다.

3개의 모든 프로젝트에 대한 총 인프라 비용은 약 $150–200/월입니다. 그것은 수백만의 월간 크롤을 받는 253,000개의 페이지를 호스팅합니다. 프로그래매틱 사이트를 구축 중이고 Next.js 개발 기능을 고려하거나 헤드리스 CMS 아키텍처에 도움이 필요하다면, 이것이 우리가 추천할 스택입니다.

프로그래매틱 SEO: Next.js & Supabase로 253K 페이지를 색인한 방법 - 아키텍처

데이터 파이프라인 아키텍처

데이터는 프로그래매틱 SEO를 만들거나 깰 것입니다. 템플릿은 쉽습니다. 수만 개의 페이지에 진정으로 고유하고 고품질의 데이터를 얻는 것입니까? 그것이 어려운 부분입니다.

우리는 프로젝트 전체에서 4가지 데이터 소스 유형을 사용합니다:

1. API 스크래핑

Not Another Sunday는 Google Places API, Yelp Fusion API, 일본을 위한 소수의 지역 API에서 장소 데이터를 끌어옵니다. 우리는 새로운 장소, 업데이트된 시간, 폐쇄된 위치를 확인하는 Supabase Edge Functions을 통해 매일 밤 동기화 작업을 실행합니다. 각 API 응답은 삽입 전에 우리의 스키마로 정규화됩니다.

2. 유효성 검사가 있는 CSV 가져오기

HostList의 초기 데이터 세트는 2년 동안 컴파일된 호스팅 회사의 거대한 CSV에서 나왔습니다. 우리는 중복을 확인하고, 회사 이름을 정규화하고, 불완전한 레코드에 플래그를 지정하는 유효성 검사 파이프라인을 구축했습니다. 초기 가져오기의 약 30%가 플래그되었고 수동 검토가 필요했습니다.

3. Claude AI 보강

여기서 흥미로워집니다. Deluxe Astrology의 경우, 우리는 기본 전기 데이터를 가진 28,840개의 유명인 레코드를 가지고 있습니다 -- 이름, 생일, 출생지. 그것은 유용한 페이지에 충분하지 않습니다. Claude (Anthropic의 API)를 사용하여 각 레코드를 출생도 해석, 성격 분석, 경력 호환성 통찰력, 재미있는 사실로 보강했습니다.

핵심: 우리는 Claude를 사용하여 아무것도 없는 콘텐츠를 _생성_하지 않았습니다. 우리는 이를 실제 천문학적 데이터를 _분석하고 해석_하는 데 사용했습니다. 각 유명인의 출생도는 그들의 출생 데이터에서 수학적으로 계산되며, Claude는 점성술 해석을 제공합니다. 기저 데이터는 고유하고 검증 가능합니다. AI 레이어는 깊이를 추가하고, 조작을 추가하지 않습니다.

다음은 우리의 보강 파이프라인의 단순화된 버전입니다:

import anthropic
from supabase import create_client

client = anthropic.Anthropic()
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)

def enrich_celebrity(record):
    natal_chart = calculate_natal_chart(
        birth_date=record['birth_date'],
        birth_place=record['birth_place']
    )
    
    prompt = f"""Given this natal chart data for {record['name']}:
    Sun: {natal_chart['sun_sign']} in {natal_chart['sun_house']}
    Moon: {natal_chart['moon_sign']} in {natal_chart['moon_house']}
    Rising: {natal_chart['ascendant']}
    
    Write a 300-word astrological personality profile focusing on 
    how these placements manifest in their career as a {record['profession']}.
    Include specific aspect interpretations."""
    
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}]
    )
    
    supabase.table('celebrities').update({
        'natal_chart': natal_chart,
        'ai_profile': response.content[0].text,
        'enriched_at': 'now()'
    }).eq('id', record['id']).execute()

우리는 약 일주일에 걸쳐 모든 28,840개의 레코드를 처리했으며, 비율 제한을 유지하기 위해 요청을 배치 처리했습니다. 비용은 API 크레딧으로 대략 $180이었습니다. 거의 29K 페이지를 고유한 콘텐츠로 보강하는 데 나쁘지 않습니다.

4. 사용자 생성 콘텐츠

Not Another Sunday는 사용자로부터 리뷰와 사진 제출을 수락합니다. 이 UGC는 시간이 지남에 따라 페이지를 점점 더 독특하게 만들고 콘텐츠가 신선하고 커뮤니티 중심이라는 신호를 Google에 보냅니다.

Google이 싫어하지 않는 페이지 템플릿 아키텍처

여기서 대부분의 프로그래매틱 SEO 프로젝트가 실패합니다. 다음과 같은 템플릿을 만듭니다:

<h1>{City} {Service} Directory</h1>
<p>Looking for {service} in {city}? Browse our directory of {count} providers.</p>

그것은 얇은 콘텐츠입니다. Google이 압니다. 사용자가 압니다. 이렇게 하지 마세요.

우리의 템플릿 아키텍처는 모든 페이지가 5가지 고유한 요소를 가지도록 보장합니다:

  1. 고유한 H1: 패턴에 삽입된 {name}이 아닙니다. H1 구조는 콘텐츠 유형에 따라 다르며 컨텍스트 수정자를 포함합니다.

  2. 고유한 메타 설명: 템플릿이 아닌 실제 페이지 데이터에서 생성됩니다.

  3. 고유한 본문 콘텐츠: 이것이 큰 것입니다. 각 페이지는 해당 엔터티에 특정한 400-2,000단어의 콘텐츠를 가지고 있습니다. 유명인의 경우, 그들의 출생도 분석입니다. 장소의 경우, 그들의 NRI 분석, 이웃 컨텍스트, 메뉴 하이라이트입니다. 호스팅 회사의 경우, 특정 가동 시간 백분율과 가격이 있는 HostScore 분석입니다.

  4. 구조화된 데이터 (schema.org): 모든 페이지는 해당 유형에 적절한 JSON-LD 마크업을 받습니다 -- 유명인의 경우 Person, 장소의 경우 LocalBusiness, 호스팅 회사의 경우 Organization.

  5. 내부 링크: 각 페이지는 실제 데이터 관계를 기반으로 5-15개의 관련 페이지에 링크됩니다. 유명인 페이지는 동일한 태양 표시, 동일한 전문 분야 또는 동일한 출생 연도를 가진 다른 유명인에게 링크됩니다. 장소 페이지는 근처 장소 및 유사한 NRI 점수를 가진 장소에 링크됩니다.

내부 링크 부분은 색인화를 위한 가장 중요한 단일 요소로 판명되었습니다. 수정 섹션에서 더 이야기하겠습니다.

실제 코드: Supabase 쿼리에서 렌더링된 페이지까지

Not Another Sunday 장소 페이지에 대한 실제 흐름을 보여드리겠습니다. 이것은 프로덕션 코드이며 가독성을 위해 약간 단순화되었습니다.

먼저, Supabase 쿼리 레이어:

// lib/queries/venues.ts
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!
)

export async function getVenueBySlug(slug: string) {
  const { data, error } = await supabase
    .from('venues')
    .select(`
      id, name, slug, description, nri_score,
      address, city, country, lat, lng,
      opening_hours, photos, menu_highlights,
      created_at, updated_at,
      venue_reviews (
        id, rating, body, author_name, created_at
      ),
      venue_tags (
        tag:tags ( name, slug )
      )
    `)
    .eq('slug', slug)
    .eq('status', 'published')
    .single()

  if (error) throw error
  return data
}

export async function getRelatedVenues(venueId: string, city: string, nriScore: number) {
  const { data } = await supabase
    .rpc('get_related_venues', {
      p_venue_id: venueId,
      p_city: city,
      p_nri_score: nriScore,
      p_limit: 12
    })

  return data ?? []
}

get_related_venues 함수는 NRI 점수 근접도별로 정렬된 근처 장소를 반환하는 Supabase의 PostgreSQL 함수입니다:

CREATE OR REPLACE FUNCTION get_related_venues(
  p_venue_id UUID,
  p_city TEXT,
  p_nri_score NUMERIC,
  p_limit INT DEFAULT 12
)
RETURNS TABLE (
  id UUID, name TEXT, slug TEXT, 
  nri_score NUMERIC, city TEXT, country TEXT
) AS $$
BEGIN
  RETURN QUERY
  SELECT v.id, v.name, v.slug, v.nri_score, v.city, v.country
  FROM venues v
  WHERE v.id != p_venue_id
    AND v.status = 'published'
    AND v.city = p_city
  ORDER BY ABS(v.nri_score - p_nri_score) ASC
  LIMIT p_limit;
END;
$$ LANGUAGE plpgsql;

이제 App Router를 사용하는 Next.js 페이지 컴포넌트:

// app/venues/[country]/[city]/[slug]/page.tsx
import { getVenueBySlug, getRelatedVenues } from '@/lib/queries/venues'
import { VenueHeader } from '@/components/venue/VenueHeader'
import { NRIScoreCard } from '@/components/venue/NRIScoreCard'
import { VenueMap } from '@/components/venue/VenueMap'
import { ReviewSection } from '@/components/venue/ReviewSection'
import { RelatedVenues } from '@/components/venue/RelatedVenues'
import { venueJsonLd } from '@/lib/schema/venue'
import { notFound } from 'next/navigation'

export const revalidate = 3600 // ISR: 1시간마다 재검증

export async function generateMetadata({ params }: Props) {
  const venue = await getVenueBySlug(params.slug)
  if (!venue) return {}

  const reviewCount = venue.venue_reviews?.length ?? 0
  const avgRating = reviewCount > 0
    ? (venue.venue_reviews.reduce((sum, r) => sum + r.rating, 0) / reviewCount).toFixed(1)
    : null

  return {
    title: `${venue.name} -- Specialty Coffee in ${venue.city} | NRI ${venue.nri_score}`,
    description: avgRating
      ? `${venue.name} in ${venue.city} scores ${venue.nri_score}/10 NRI. Rated ${avgRating}/5 from ${reviewCount} reviews. ${venue.description?.slice(0, 80)}...`
      : `${venue.name} in ${venue.city} scores ${venue.nri_score}/10 on our Neighbourhood Relevance Index. ${venue.description?.slice(0, 100)}...`,
    alternates: {
      canonical: `/venues/${params.country}/${params.city}/${params.slug}`
    }
  }
}

export default async function VenuePage({ params }: Props) {
  const venue = await getVenueBySlug(params.slug)
  if (!venue) notFound()

  const related = await getRelatedVenues(venue.id, venue.city, venue.nri_score)

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(venueJsonLd(venue)) }}
      />
      <article>
        <VenueHeader venue={venue} />
        <NRIScoreCard score={venue.nri_score} breakdown={venue.nri_breakdown} />
        <VenueMap lat={venue.lat} lng={venue.lng} />
        <section className="venue-body">
          <h2>About {venue.name}</h2>
          <p>{venue.description}</p>
          {venue.menu_highlights && (
            <>
              <h3>Menu Highlights</h3>
              <ul>
                {venue.menu_highlights.map(item => (
                  <li key={item}>{item}</li>
                ))}
              </ul>
            </>
          )}
        </section>
        <ReviewSection reviews={venue.venue_reviews} />
        <RelatedVenues venues={related} currentCity={venue.city} />
      </article>
    </>
  )
}

revalidate = 3600을 주의하세요. 이것이 ISR입니다 -- 페이지는 첫 번째 요청 시 정적으로 생성되고 1시간 동안 캐시됩니다. Google의 크롤러는 항상 빠른 HTML을 얻습니다. 신선한 데이터는 다음 재검증 주기에 흐릅니다. 이것은 크롤 예산에 엄청나게 중요합니다.

무엇이 깨졌고 어떻게 고쳤는가

여기서 대부분의 사례 연구가 솔직하지 않습니다. 디버깅의 몇 달 없이 결과를 보여줍니다. 우리는 3가지 주요 문제가 있었습니다.

문제 1: Deluxe Astrology -- 크롤 예산 기아

우리는 91,000개의 페이지와 평면 사이트맵 구조로 시작했습니다. Google은 첫 번째 달에 약 12,000개의 페이지를 색인했고 그 다음... 멈췄습니다. GSC 커버리지 보고서는 수만 개의 URL에 대해 "발견됨 -- 현재 색인되지 않음"을 보여주었습니다.

문제는 두 가지였습니다. 첫 번째, 우리의 사이트맵은 91,000개의 URL이 있는 단일 파일이었습니다. Google은 사이트맵당 최대 50,000개를 권장하지만, 그 제한 내에서도 단일 거대한 사이트맵은 우선 순위를 신호하지 않습니다. 두 번째, 우리의 내부 링크가 약했습니다 -- 많은 페이지는 사이트맵을 통해서만 도달할 수 있었고, 페이지의 링크를 통해서는 도달할 수 없었습니다.

수정:

  1. 사이트맵 재구조화: 우리는 모놀리식 사이트맵을 카테고리 기반 사이트맵으로 나누었습니다. sitemap-celebrities.xml, sitemap-horoscopes-en.xml, sitemap-horoscopes-es.xml 등. 각각 10,000개 미만의 URL.

  2. 내부 링크 대전: 우리는 모든 페이지에 문맥적 교차 링크를 추가했습니다. 유명인 페이지는 이제 관련된 유명인(동일한 조디악, 동일한 직업, 동일한 출생 연도)에 링크됩니다. 운세 페이지는 해당 표시의 유명인 프로필에 링크됩니다. 모든 페이지는 최소 8개의 다른 페이지에 연결됩니다.

  3. 얇은 페이지 제거: 우리는 고유한 콘텐츠가 200단어 미만인 약 4,000개의 페이지를 제거했습니다. 이들은 주로 가치를 추가하지 않는 자동 생성 조합 페이지였습니다. 더 적은 페이지이지만 더 높은 품질입니다.

이러한 변경 후, 색인화는 약 10주에 걸쳐 12K에서 91K로 상승했습니다. 내부 링크가 가장 큰 레버였습니다.

문제 2: HostList -- ISR 잘못된 구성

HostList는 모든 페이지에 export const dynamic = 'force-dynamic'으로 시작했습니다. 이는 모든 단일 요청 -- Googlebot 크롤 포함 -- Supabase를 실시간으로 뜻했습니다. Google이 매일 수천 개의 페이지를 크롤링하면서 우리의 Supabase 인스턴스가 두드려지고, 응답 시간이 급증했으며, 일부 페이지는 크롤 중에 시간 초과되었습니다.

수정: 우리는 export const revalidate = 3600으로 전환했습니다. 페이지는 정적으로 캐시되고 100ms 미만에 제공됩니다. Supabase는 요청당 매번이 아니라 페이지당 시간당 한 번만 히트됩니다. 우리의 p95 응답 시간은 2.8초에서 47밀리초로 떨어졌습니다. Googlebot은 기다리지 않았기 때문에 매일 3배 더 많은 페이지를 크롤링하기 시작했습니다.

문제 3: Not Another Sunday -- 국가 전체의 중복 콘텐츠

일부 카페 체인이 여러 국가에서 운영됩니다. 도쿄의 Starbucks Reserve와 런던의 Starbucks Reserve는 처음에 템플릿이 위치별 데이터보다 브랜드 정보를 강조했기 때문에 매우 유사한 페이지 콘텐츠를 가지고 있었습니다.

수정: 우리는 위치별 콘텐츠에 훨씬 더 높은 가중치를 부여했습니다. 이웃 설명, 근처 장소 비교, 지역 리뷰 감정, 국가별 가격이 이제 각 페이지의 70% 이상을 차지합니다. 브랜드 정보는 작은 섹션입니다. Google은 더 이상 이들을 거의 중복으로 플래그하지 않습니다.

결과: 하키스틱 곡선과 솔직한 실패

3개의 모든 프로젝트 전체의 결합된 GSC 데이터는 고전적인 하키스틱 곡선을 보여줍니다 -- 수주간 평평하다가 Google의 크롤러가 우리 도메인에 대한 신뢰를 얻으면서 지수 성장입니다.

지표 1월 3월 6월 12월
총 색인 페이지 18,200 67,000 189,000 253,000
일일 유기 클릭 340 2,100 8,400 19,600
평균 위치 (모든 쿼리) 42 28 16 11
크롤 요청/일 (모든 사이트) 4,200 12,800 31,000 48,000
월간 Supabase 비용 $75 $75 $125 $150
월간 Vercel 비용 $40 $60 $60 $60

하지만 실패에 대해서도 솔직하겠습니다. 우리 페이지의 약 8%은 12개월 후에도 "발견됨 -- 현재 색인되지 않음"에 머물러 있습니다. 이들은 장거리의 가장 낮은 트래픽 가능성 페이지가 되는 경향이 있습니다 -- 낮은 검색량 언어의 특정 천사 숫자 페이지 또는 소규모 시장의 호스팅 회사입니다. 더 많은 내부 링크로 강제 색인할 수 있지만 ROI가 그곳에 없습니다.

우리는 또한 Google 핵심 업데이트 이후 4개월경 Deluxe Astrology의 트래픽이 30% 떨어진 기간이 있었습니다. 우리 끝에서 변경 없이 6주에 걸쳐 회복되었지만 그 주들은 스트레스가 많았습니다. 프로그래매틱 사이트는 Google이 전체 페이지 말뭉치에 걸쳐 품질 신호를 다시 평가하는 핵심 업데이트 중에 더 휘발성이 있는 것 같습니다.

이 규모로 무언가 구축을 고려 중이라면, 우리의 접근 방식과 가격을 우리의 가격 페이지에 자세히 설명했습니다. 순수 정적 pSEO를 위한 Astro 기반 정적 사이트 생성의 경우 -- 우리도 실험했습니다 -- Astro 개발 기능을 확인하세요.

프로그래매틱 SEO vs. 전통적인 콘텐츠: 어떤 것을 언제 사용할 것인가

프로그래매틱 SEO는 편집 콘텐츠를 대체하지 않습니다. 다른 작업을 위한 다른 도구입니다.

요소 프로그래매틱 SEO 전통적인 콘텐츠
최고의 용도 데이터 기반 쿼리 ("Shibuya에서 최고의 카페", "Leo 오늘의 운세") 의도 기반 쿼리 ("드립 커피 추출하는 방법")
콘텐츠 고유성 페이지당 고유한 데이터에서 나옵니다 고유한 관점/연구에서 나옵니다
확장 속도 주당 1,000+ 페이지 주당 2-5개 기사
유지보수 부담 데이터베이스 업데이트, 템플릿 수정 주기적 콘텐츠 새로고침
Google 신뢰 구축 느린 (규모에서 품질을 증명해야 함) 빠른 (각 조각이 개별적으로 판단됨)
위험 프로필 더 높음 (얇은 콘텐츠 페널티가 전체 사이트에 영향) 더 낮음 (하나의 나쁜 기사가 도메인을 망치지 않음)

최적의 지점은 둘을 결합하는 것입니다. Not Another Sunday는 137K의 프로그래매틱 장소 페이지 커피 문화, 추출 방법, 도시별 카페 크롤 경로에 대한 200개 이상의 편집 가이드를 가지고 있습니다. 편집 콘텐츠는 전체 도메인을 상승시키는 E-E-A-T 신호를 구축하며, 이는 프로그래매틱 페이지가 더 빨리 색인되도록 돕습니다.

FAQ

프로그래매틱 SEO로 현실적으로 얼마나 많은 페이지를 색인할 수 있습니까?

이는 전적으로 도메인 권한과 콘텐츠 품질에 달려 있습니다. 강력한 백링크 프로필이 있는 확립된 도메인에서, 우리는 100K+ 페이지에 대해 90% 이상의 색인화율을 봤습니다. 새 도메인은 투쟁합니다 -- dev.to 사례 연구 287K 페이지가 신선한 도메인에서 거의 0에 가까운 색인화를 받는 것이 예외가 아니라 규범입니다. 1,000-5,000개의 고품질 페이지로 시작하고, 권한을 구축한 다음, 확장하세요.

얇은 콘텐츠 페널티를 피하기 위한 페이지당 최소 콘텐츠는 얼마입니까?

우리는 페이지당 최소 400단어의 고유한 콘텐츠, 구조화된 데이터, 이미지, 내부 링크를 목표로 합니다. 하지만 단어 수만이 메트릭이 아닙니다 -- 페이지가 사용자의 쿼리에 이미 존재하는 것보다 더 잘 답하는지 여부입니다. 고유한 데이터 테이블과 지도가 있는 200단어 페이지는 일반 텍스트의 2,000단어 페이지를 능가할 수 있습니다.

프로그래매틱 SEO는 여전히 Google의 2025년 유용한 콘텐츠 업데이트 이후 안전합니까?

예, 당신이 실제로 유용한 페이지를 만들고 있다면. Google의 2025년 업데이트는 검색 트래픽을 캡처하기 위해서만 존재하고 가치를 제공하지 않는 저품질 프로그래매틱 콘텐츠를 구체적으로 대상으로 합니다. Zapier (70K 페이지, $140M ARR)와 같은 사이트는 그들의 페이지가 실제 문제를 해결하기 때문에 계속 번성합니다. 페널티를 받는 사이트들은 검색 트래픽을 생성하는 "{service} in {city}"의 변형을 생성하는 것들입니다.

Supabase와 Vercel을 포함한 프로그래매틱 SEO 스택의 비용은 얼마입니까?

우리의 3개 프로젝트 스택은 총 약 $150-200/월로 실행됩니다. Supabase Pro는 프로젝트당 $25/월입니다 (우리는 3개 인스턴스 사용). Vercel Pro는 사용자당 $20/월입니다. Claude's API를 통한 AI 보강은 28,840개 레코드에 대한 일회성 $180 비용이었습니다. 50K 페이지 미만의 대부분의 프로젝트의 경우 $50-100/월의 인프라 비용을 예상하세요.

Google이 프로그래매틱 페이지를 색인하는 데 얼마나 걸립니까?

사이트맵 초기 크롤링의 경우 2-4주를 예상하지만, 큰 페이지 세트의 전체 색인화는 3-6개월이 걸립니다. 우리의 경험은 하키스틱 패턴을 보여줍니다: Google이 품질을 평가할 때 처음 6-8주는 느린 크롤링, 그 다음 색인할 가치가 있는 콘텐츠라고 결정하면 빠른 가속입니다. 내부 링크와 사이트맵 구조는 이 타임라인에 극적으로 영향을 줍니다.

프로그래매틱 SEO 페이지에 Next.js SSR 또는 ISR을 사용해야 합니까?

ISR, 거의 항상. SSR (force-dynamic)은 모든 크롤러 요청 -- Googlebot 크롤 포함 -- 데이터베이스를 뜻하며, 이는 규모에서 성능 문제를 만들고 느린 응답에 대한 크롤 예산을 낭비합니다. revalidate = 3600 (또는 일일 업데이트의 경우 86400인 경우에도)이 있는 ISR은 동적 데이터 신선성을 가진 정적 사이트 성능을 제공합니다. 우리는 HostList에서 이것을 어렵게 배웠습니다 -- force-dynamic에서 ISR로 전환하면 응답 시간이 2.8초에서 47밀리초로 떨어졌습니다.

100K+ 페이지 전체의 내부 링크를 어떻게 처리합니까?

데이터베이스 기반 관련 콘텐츠 쿼리. 모든 페이지는 실제 데이터 관계를 기반으로 8-15개의 관련 페이지를 찾는 쿼리를 실행합니다 -- 동일한 카테고리, 유사한 점수, 지리적 근접성, 공유된 속성. 페이지에 무작위로 링크하지 마세요. 링크는 사용자와 Google 모두에게 문맥적 의미가 있어야 합니다. 우리는 이러한 관계를 효율적으로 계산하기 위해 Supabase의 PostgreSQL 함수를 사용합니다.

프로그래매틱 SEO로 사람들이 하는 가장 큰 실수는 무엇입니까?

페이지 품질 대신 페이지 수에 집중하는 것입니다. 10,000개의 우수한 페이지가 100,000개의 평범한 페이지를 능가할 것입니다. 우리는 Deluxe Astrology에서 4,000개의 얇은 페이지를 제거했고 나머지 페이지에서 색인화가 _증가_했습니다. Google은 얇은 페이지를 당신의 전체 사이트가 낮은 품질일 수 있다는 신호로 해석합니다. 올바른 방법으로 프로그래매틱 페이지를 구축할 준비가 되었다면, 우리 팀에 연락하세요 -- 우리는 이러한 교훈을 배웠으므로 당신이 배울 필요가 없습니다.