--- kind: CASE slug: a-route-the-web-server-never-heard-of title: nginx 가 모르는 라우트는 새로고침에서 404 다 topic: one-route-many-hand-kept-lists topicName: 라우트 하나가 울리는 손 목록 project: TechLog status: 게시 전 lastVerifiedOn: 2026-09-04 sourceRevision: tech-log@2026-09-02 source: - final/document.md#§8.2 - final/document.md#§12.6 --- # nginx 가 모르는 라우트는 새로고침에서 404 다 `/studio/releases` 가 평문 404 를 돌려줬다. 라우트는 있고 청크도 빌드됐고 SPA 내부 이동으로는 화면에 닿는데, 하드 로드와 새로고침은 거기까지 가지 못한다. nginx 설정이 손으로 유지하는 배열에서 나오고 있었다. ## 관계 - **라우트에 딸린 목록은 라우트 계약에서 유도하고, 유도할 수 없는 것은 대조 검사를 둔다** 이 사건에서 굳힌 기준이다. - **catch-all 라우트는 nginx 패턴으로 번역하지 않는다** 유도 규칙에서 함께 정한 것이다. - **라우트 하나가 건드리는 여덟 곳과, 그것들이 우는 시점** 같은 부류가 다른 목록에서 어떻게 우는지가 그 기록에 있다. ## 문제 SPA 안에서 이동하면 화면이 열린다. 주소창에 그 주소를 직접 넣거나 새로고침하면 nginx 가 평문 404 를 준다. 웹 서버가 그 경로의 존재를 들은 적이 없기 때문이다. 서빙 계약이 어느 경로를 SPA 로 넘길지 정하는데, 그 목록에 없는 경로는 넘어가지 않는다. ## 결론 서빙 계약의 절반은 라우트 레지스트리에서 유도하고 있었고 나머지 절반은 손으로 유지하는 배열이었다. > 서빙 계약의 공개 절반은 라우트 레지스트리에서 패턴을 유도한다. **Studio 절반은 손으로 유지하는 배열이었고, 손으로 유지하는 배열이 실패하는 방식 그대로 실패했다** — `^/studio/assets$` 위의 주석이 바로 그 버그를 한 번 고친 기록이고, 라우트를 더하니 즉시 반복됐다. 공개 절반도 유도라고 하기 어려웠다. 번들된 픽스처에 우연히 들어 있던 공개 경로를 전부 열거하고, 생성된 nginx 가 정확히 그것들을 `location =` 블록으로 게시했다. 빌드 이후에 게시된 기록 — 백엔드를 두는 이유 그 자체 — 은 SPA 에 묻기도 전에 엣지에서 404 였다. 경로 27개가 얼어 있었고 28번째는 무엇이든 닿을 수 없었다. 지금은 라우트 계약에서 등록된 Public 라우트마다 정규식 하나를 만든다. 파라미터는 한 세그먼트만 잡고 슬래시는 잡지 않으므로 `/cases/a/b` 는 404 로 남는다. ## 검증 환경 tech-log-frontend : ab8c6c1 · 6784eb1 엣지 : nginx (서빙 계약에서 생성) 확인 방식 : 생성된 서빙 패턴이 라우트 계약과 같은지 보는 테스트 ## 재현 조건 1. 라우트를 하나 더하고 서빙 계약 배열에는 넣지 않는다 2. 빌드하고 배포한 뒤 그 주소를 주소창에 직접 넣는다 — 평문 404 가 온다 3. SPA 안에서 링크로 이동한다 — 화면이 열린다 ## 본문 ## 내부 이동은 되고 하드 로드는 안 된다 SPA 안에서 이동할 때는 라우터가 화면을 그리므로 웹 서버가 개입하지 않는다. 주소를 직접 넣거나 새로고침하면 웹 서버가 먼저 그 경로를 받는다. 서빙 계약에 그 경로가 없으면 nginx 는 SPA 로 넘기지 않고 404 를 준다. 그래서 「내부에서는 되는데 새로고침하면 안 된다」로 나타난다. ## 손으로 유지하는 절반 > 서빙 계약의 공개 절반은 라우트 레지스트리에서 패턴을 유도한다. **Studio 절반은 손으로 유지하는 배열이었고, 손으로 유지하는 배열이 실패하는 방식 그대로 실패했다** — `^/studio/assets$` 위의 주석이 바로 그 버그를 한 번 고친 기록이고, 라우트를 더하니 즉시 반복됐다. ## 얼어붙은 27개 공개 절반도 유도가 아니었다. 서빙 계약이 번들된 픽스처에 우연히 들어 있던 공개 경로를 전부 열거하고, 생성된 nginx 가 정확히 그것들을 `location =` 블록으로 게시했다. 빌드 이후에 게시된 기록은 SPA 에 묻기도 전에 엣지에서 404 였다. 경로 27개가 얼어 있었고 28번째는 무엇이든 닿을 수 없었다. ## 라우트 계약에서 유도한다 지금은 라우트 계약에서 등록된 Public 라우트마다 정규식 하나를 만든다. 파라미터는 한 세그먼트만 잡고 슬래시는 잡지 않으므로 `/cases/a/b` 는 404 로 남는다. 같은 구조 때문에 `robots.txt` 도 404 였다. 파일은 이미지에 있었지만 nginx 설정이 서빙할 파일을 하나씩 명시하는 구조라 등록되지 않은 것은 SPA 폴백으로 떨어진다. 크롤러가 index.html 을 규칙으로 읽을 수는 없다. ## 확인하지 못한 것 경로 27개가 얼어 있던 상태의 생성 결과물은 남기지 않았다. 지금 생성되는 패턴이 라우트 계약과 같은지만 테스트가 본다.