SSG에서 ?page=2가 동작하지 않는 이유

SSG 파일 기반 라우팅과 Astro paginate() 설계 철학

BuildAstroRendering

난이도: ★★☆☆☆
연관 노트: 크롤러가 CSR 페이지를 못 읽는 이유


핵심 요약

  1. SSR/CSR에서 쿼리파라미터는 런타임에 서버가 읽는다. ?page=2 요청을 받아서 2페이지 데이터를 응답하는 주체가 있다.
  2. SSG는 빌드 타임에 파일을 만들고 끝이다. 정적 파일 서버는 쿼리스트링을 무시하고 파일 경로만 본다. /notes?page=2/notes?foo=barnotes/index.html 하나를 돌려준다.
  3. 따라서 SSG 페이지네이션은 “동적으로 데이터를 필터링”하는 게 아니라 “빌드 타임에 offset으로 잘라서 각 페이지를 별도 HTML 파일로 미리 생성”하는 방식이다.

왜 헷갈렸나

쿼리파라미터로 상태를 URL에 담는 패턴(?page=2, ?tab=recent)에 익숙해서 페이지네이션도 같은 방식으로 접근했다. 하지만 이 패턴은 “쿼리를 읽을 서버나 클라이언트 JS가 있다”는 전제가 깔려 있다. SSG에선 그 전제 자체가 없다.


메커니즘

SSR/CSR — 쿼리파라미터가 동작하는 구조

브라우저 → /notes?page=2 요청

           런타임 서버 (Node.js, Next.js API 등)

           page=2 파싱 → DB/CMS에서 11~20번째 데이터 조회

           HTML 응답

SSG — 빌드 타임 파일 생성 구조

빌드 타임:
  getStaticPaths + paginate()

  전체 posts를 10개씩 offset으로 분할

  /notes/index.html   (1~10번)
  /notes/2/index.html (11~20번)
  /notes/3/index.html (21~30번)

런타임:
  브라우저 → /notes/2 요청

           CDN/정적 서버

           /notes/2/index.html 파일 그대로 응답
           (쿼리스트링은 무시, 서버 로직 없음)

Astro의 설계 철학

Next.js getStaticPaths vs Astro getStaticPaths

같은 이름이지만 역할이 다르다.

Next.jsAstro
역할동적 라우트 경로를 “알려주는” 것경로 생성 + 데이터 변환까지 담당
페이지네이션별도 로직 직접 구현paginate() 내장 함수 제공
반환값{ paths, fallback }paginate(data, { pageSize })
// Astro: getStaticPaths가 데이터 변환(paginate)까지 책임진다
export async function getStaticPaths({ paginate }) {
  const posts = await getCollection('notes')
  return paginate(posts, { pageSize: 10 })  // 경로 + 데이터 슬라이싱 동시에
}

파일명이 곧 URL 스펙

  • [page].astro/notes/1, /notes/2 (1부터 시작하는 숫자 세그먼트)
  • [...page].astro/notes (1페이지), /notes/2, /notes/3 (rest param, 루트 URL이 1페이지)

파일명 하나가 URL 구조 전체를 결정한다. 이 규칙을 모르면 /notes는 되는데 /notes/1이 안 되는 상황을 디버깅하기 어렵다.

글이 추가되면?

빌드를 다시 돌리면 paginate()가 자동으로 재분배한다. 글 18개 → 2페이지, 글 21개 → 3페이지. 정적 파일이지만 빌드 트리거 = 자동 재생성.


다음에 이 상황을 만나면

SSG 프레임워크에서 목록에 페이지네이션이 필요할 때 → 이렇게 접근:

  • 쿼리파라미터 방식 제외 — 정적 파일 서버는 쿼리스트링을 무시한다
  • 파일 기반 라우팅으로 설계 — URL 경로 = 빌드된 HTML 파일
  • 프레임워크의 내장 paginate API 확인 — Astro는 paginate(), Next.js는 직접 구현
  • 파일명 컨벤션 확인 — [page] vs [...page]로 루트 URL 처리 방식이 달라짐

커넥팅 닷

← 선행 개념 (이걸 알아야 이해된다)

  • 크롤러가 CSR 페이지를 못 읽는 이유 — CSR·SSR·SSG 렌더링 패러다임 차이를 먼저 이해해야 “서버가 없다”는 게 뭔지 감이 온다
  • HTTP 요청/응답 모델 — 쿼리스트링은 서버가 파싱하는 것. 서버가 없으면 읽을 주체가 없다

→ 확장 개념 (여기서 더 나아가면)

  • ISR (Incremental Static Regeneration) — SSG의 “빌드 후 재생성 불가” 한계를 극복하는 Next.js 패턴. 빌드 타임 + 런타임 재생성 혼합
  • Astro Islands Architecture — SSG 기반에서 특정 컴포넌트만 인터랙티브하게 만드는 방식. “서버 없음”의 제약을 부분적으로 우회

↔ 같은 원리가 적용되는 곳


참고