문제

React SPA를 GitHub Pages에 배포 → /cases/proof-hub-rebuild 깊은 링크 새로고침 → 404.

원인: GitHub Pages는 정적 파일 서버라 /cases/proof-hub-rebuild 경로 자체를 모름. SPA 라우팅은 클라이언트에서만 동작하므로 서버가 /index.html을 못 반환.

해결 — spa-github-pages 패턴

public/404.html에 redirect 스크립트:

<script>
  var l = window.location;
  l.replace(
    l.protocol + '//' + l.hostname + l.pathname.split('/').slice(0, 1).join('/') +
    '/?/' + l.pathname.slice(1) + (l.search ? '&' + l.search.slice(1) : '') + l.hash,
  );
</script>

index.html <head>에 복원 스크립트:

(function (l) {
  if (l.search[1] === '/') {
    var decoded = l.search.slice(1).split('&').join('?');
    window.history.replaceState(null, null, l.pathname.slice(0, -1) + decoded + l.hash);
  }
})(window.location);

흐름: 404 → 쿼리로 변환 → SPA mount → history API로 URL 복원.

이 사이트에서 — 실제 적용 기록

이 노트는 일반론이 아니라 이 사이트가 지금 쓰는 패턴이다. justinjeong5.github.io는 user site(*.github.io)라 pathSegmentsToKeep = 0. public/404.html이 redirect를, index.html <head>가 복원을 담당한다.

실제로 겪은 두 가지:

  • 빌드 산출물에 404.html이 들어가야 한다: Vite public/에 두면 dist/404.html로 복사된다. 처음엔 src에 뒀다가 dist에 안 들어가 deep-link가 계속 404. public/이 정답.
  • 로컬 dev에선 재현 안 됨: Vite dev 서버는 SPA fallback을 자동 처리해서 /cases/x 새로고침이 로컬에선 멀쩡하다. GitHub Pages 배포 후에야 404가 드러난다. 그래서 로컬 통과 = 안전이 아니다 — 배포 후 deep-link 새로고침을 반드시 수동 확인.

이 트릭 덕분에 /cases/*·/notes/* 깊은 링크를 공유해도 새로고침이 깨지지 않는다.

함정

  • pathSegmentsToKeep — user/org site (*.github.io)는 0, project site는 1
  • 검색엔진은 404 redirect를 그대로 따라오지 못할 수 있음 (SEO 약함)
  • 메타 OG 태그는 모두 index.html 기본값만 적용 (페이지별 동적 OG 불가)

대안

진짜 SEO·동적 OG가 필요하면 Next.js SSG 또는 Astro로 빌드 후 GitHub Pages. 다만 빌드 복잡도 ↑.

관련

/notes/mdx-content-as-files — 콘텐츠를 파일로 두고 라우팅과 분리하면 SPA fallback과도 잘 맞물린다 /notes/vite-manual-chunks — fallback 흐름에 함께 영향 받는 첫 로드 청크 크기 다루기 /cases/proof-hub-rebuild — 이 라우팅이 적용된 사이트 리빌드 케이스