SSG에서 ?page=2가 동작하지 않는 이유
SSG 파일 기반 라우팅과 Astro paginate() 설계 철학
난이도: ★★☆☆☆
연관 노트: 크롤러가 CSR 페이지를 못 읽는 이유
핵심 요약
- SSR/CSR에서 쿼리파라미터는 런타임에 서버가 읽는다.
?page=2요청을 받아서 2페이지 데이터를 응답하는 주체가 있다. - SSG는 빌드 타임에 파일을 만들고 끝이다. 정적 파일 서버는 쿼리스트링을 무시하고 파일 경로만 본다.
/notes?page=2든/notes?foo=bar든notes/index.html하나를 돌려준다. - 따라서 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.js | Astro | |
|---|---|---|
| 역할 | 동적 라우트 경로를 “알려주는” 것 | 경로 생성 + 데이터 변환까지 담당 |
| 페이지네이션 | 별도 로직 직접 구현 | 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 기반에서 특정 컴포넌트만 인터랙티브하게 만드는 방식. “서버 없음”의 제약을 부분적으로 우회
↔ 같은 원리가 적용되는 곳
- 쿼리파라미터가 붙은 URL에서 탭 active가 깨지는 이유 — URL 구조를 잘못 이해하면 라우팅 비교 로직이 틀린다는 같은 원인
- pages/ 안에 컴포넌트를 두면 안 되는 이유 — “파일 경로 = URL”이라는 Pages Router 규칙이 컴포넌트 배치에도 그대로 영향을 준다
- GitHub Pages / Netlify 정적 배포 — URL = 파일 경로가 강제되는 환경. 404 처리도 파일 기반으로 해야 한다
참고
- Astro Pagination 공식 문서 —
paginate()API 레퍼런스