🏝️ Hwani Studio

Home › Guides › 1인 메이커 개발 노트

Firebase Hosting의 catch-all rewrite와 soft 404: robots.txt·ads.txt가 HTML로 나올 때

2026-10-18 · Hwani Studio

Firebase Hosting에서 단일 페이지 앱(SPA)을 배포할 때 가장 많이 쓰는 설정이 아래의 catch-all rewrite입니다. 이 사이트(alt5713.com)도 처음에는 이 설정이었습니다.

"rewrites": [
  { "source": "**", "destination": "/index.html" }
]

클라이언트 라우터가 /song/123 같은 주소를 직접 처리하려면 필요한 설정입니다. 하지만 이 설정에는 눈에 잘 띄지 않는 부작용이 있습니다.

파일이 먼저, rewrite는 나중

Firebase Hosting은 요청이 오면 먼저 배포된 정적 파일 중에 일치하는 것이 있는지 찾습니다. 파일이 있으면 그대로 돌려주고, 없을 때만 rewrite 규칙을 적용합니다. 그래서 robots.txt 파일을 실제로 배포했다면 catch-all이 있어도 robots.txt가 정상적으로 나옵니다.

문제는 파일이 없을 때입니다. catch-all 때문에 없는 경로도 모두 index.html로 바뀌고, 상태 코드는 200입니다. 브라우저로 열어 보면 홈 화면이 나오니 문제를 알아차리기 어렵습니다. 하지만 검색 엔진과 광고 크롤러 입장에서는 다음과 같이 보입니다.

curl로 확인하기

눈으로 보지 말고 응답 헤더를 확인하세요.

curl -sI https://example.com/robots.txt | grep -i -E "^HTTP|content-type"
curl -sI https://example.com/ads.txt    | grep -i -E "^HTTP|content-type"
curl -sI https://example.com/this-page-does-not-exist | head -1

정상이라면 robots.txt와 ads.txt는 200과 text/plain, sitemap.xml은 XML 형식, 없는 페이지는 404가 나와야 합니다. 셋 다 text/html이면 catch-all이 파일 부재를 가리고 있는 것입니다.

정리 방법 1: 라우터가 없다면 catch-all을 지운다

이 사이트의 홈 화면은 주소를 바꾸지 않고 탭으로 화면을 전환합니다. 경로 기반 라우팅을 쓰지 않으므로 catch-all이 필요 없었습니다. 그래서 rewrite를 지우고 다음처럼 바꿨습니다.

"hosting": {
  "public": ".",
  "cleanUrls": true,
  "trailingSlash": false
}

cleanUrls를 켜면 privacy.html이 /privacy로 서빙됩니다. 폴더의 index.html은 폴더 주소(/guides/)로 열립니다. 루트에 404.html을 두면 없는 경로에 진짜 404 상태 코드와 함께 그 페이지가 나옵니다.

정리 방법 2: 라우터가 있다면 경로를 좁힌다

클라이언트 라우팅이 꼭 필요하다면 catch-all 대신 실제로 쓰는 경로만 rewrite합니다. 예를 들어 { "source": "/app/**", "destination": "/app/index.html" }처럼 앱 영역을 한정하면, 그 밖의 없는 경로는 404가 됩니다. 그리고 robots.txt, sitemap.xml, ads.txt를 반드시 실제 파일로 배포합니다.

public 폴더가 저장소 루트일 때

"public": "."처럼 저장소 루트를 그대로 배포하면 메모용 Markdown이나 스크립트까지 공개됩니다. ignore에 **/*.md, scripts/**, docs/**처럼 공개하지 않을 파일을 넣어 두세요. 이 사이트의 가이드 페이지를 만드는 스크립트도 이렇게 배포에서 제외했습니다.

체크리스트

  1. robots.txt, sitemap.xml, ads.txt가 실제 파일로 있는지
  2. curl로 상태 코드와 Content-Type 확인
  3. 없는 경로가 404인지
  4. 공개하면 안 되는 파일이 ignore에 들어 있는지