웹사이트나 앱을 배포한 뒤 404 오류 해결하는 법
앱이 로컬에서는 잘 동작하는데 배포 후 페이지가 404를 반환한다면, 가장 흔한 원인은 싱글 페이지 앱(SPA) 라우팅이 빠져 있는 거예요. 서버가 /dashboard라는 파일을 찾는데, 그 경로는 JavaScript 안에만 존재하기 때문에 실제 파일이 없어요. 해결책은 앱 경로에 대해 index.html을 제공하는 폴백 규칙이에요. 그 밖의 원인으로는 빌드 결과물에서 빠진 파일, 대소문자가 맞지 않는 경로, 트레일링 슬래시 차이, 잘못된 기본 경로(base path), 그리고 배포되지 않은 API 경로가 있어요.
먼저 정확히 어떤 URL이 404를 반환하는지, 그리고 그 URL에 어떻게 도달했는지 적어 두세요. 그 패턴이 원인을 바로 가리켜 줘요.
1단계: 패턴 찾기
| 보이는 현상 | 가장 가능성 높은 원인 | 이동 |
|---|---|---|
| 홈페이지는 잘 되고 링크 클릭도 잘 되지만, 새로 고침하거나 깊은 링크를 열면 404 | SPA 폴백 없음 | 2단계 |
| 모든 페이지가 404, 홈페이지 포함 | 잘못된 폴더가 게시되었거나 빌드가 배포되지 않음 | 3단계 |
| 페이지는 열리지만 이미지, 폰트, 스크립트가 404 | 빌드에 파일 누락, 또는 경로 오류 | 4단계 |
/about/은 되는데 /about은 실패(또는 반대) | 트레일링 슬래시 처리 | 5단계 |
/about은 되는데 /About은 실패 | 대소문자를 구분하는 서버 | 4단계 |
페이지는 열리지만 /api/... 데이터 호출이 404 | API가 배포되지 않았거나 라우팅되지 않음 | 6단계 |
| 이전 사이트의 옛 URL이 404 | 리다이렉트 누락 | 7단계 |
| 커스텀 도메인에서만 발생 | DNS 또는 도메인 설정 | 8단계 |
실제 상태 코드를 보려면 개발자 도구(우클릭 → 검사)를 열고 네트워크 탭으로 가서 새로 고침한 뒤 첫 번째 요청의 상태를 확인하세요.
2단계: SPA 폴백 추가하기
이것이 고전적인 원인이에요. React, Vue, Svelte 싱글 페이지 앱에서 서버에는 index.html과 일부 JavaScript, CSS 같은 몇 개의 실제 파일만 있어요. /dashboard나 /projects/42 같은 경로는 브라우저의 라우터가 처리해요.
- 링크를 클릭하면 잘 동작해요. 라우터가 URL을 바꾸고 서버에 묻지 않고 새 페이지를 그리니까요.
- 새로 고침하거나 링크를 직접 열면 실패해요. 브라우저가 서버에
/dashboard를 요청하고, 서버에는 그런 파일이 없으니 404를 반환해요.
해결책은 “요청이 실제 파일과 일치하지 않으면 status 200으로 index.html을 제공하고, 라우터가 이어받게 하라”는 규칙이에요. 방법은 호스트에 따라 달라요(세부 내용이 바뀌므로 호스트의 최신 문서를 확인하세요).
| 호스트 | 일반적인 폴백 규칙 |
|---|---|
| Netlify | 게시 폴더에 /* /index.html 200이라고 적은 _redirects 파일 |
| Vercel | 모든 경로를 /index.html로 보내는 vercel.json의 리라이트(프레임워크 프리셋이 보통 자동 처리) |
| Cloudflare Pages | 최상위 404.html이 없으면 기본적으로 SPA 스타일 폴백을 제공 |
| Nginx | location / 블록에 try_files $uri $uri/ /index.html; |
| Apache | 파일이 아닌 요청을 index.html로 보내는 .htaccess의 리라이트 규칙 |
| GitHub Pages | 서버 측 리라이트 없음. 흔한 우회책은 해시 라우팅이나 리다이렉트하는 404.html |
이 규칙이 실제 파일까지 삼켜 버리지 않도록 하세요. /assets/app.js 요청은 여전히 JavaScript 파일을 반환해야 해요. 대신 index.html을 반환하면 “Failed to load module script” 오류와 함께 빈 페이지가 나와요. 흰 화면 해결하는 법을 참고하세요.
또한 라우터에 캐치올 경로를 두어, 어떤 페이지와도 맞지 않는 URL이 아무것도 없는 화면 대신 친절한 “페이지를 찾을 수 없음” 화면을 보여 주게 하세요.
서버 설정을 바꿀 수 없다면 해시 라우팅(/#/dashboard 같은 URL)을 쓰면 문제를 피할 수 있어요. # 뒤의 부분은 서버로 전송되지 않기 때문이에요. 이는 첫 번째 선택이 아니라 우회책이에요. URL이 보기 불편하고 검색엔진에도 덜 친화적이에요.
서버에서 페이지를 렌더링하거나 빌드 시 페이지를 생성하는 프레임워크(Next.js, Astro 등)는 보통 각 페이지에 실제 파일이나 서버 경로가 있어서, 이 문제가 덜 흔해요. 이들의 대응하는 문제는 빌드 시 생성되지 않은 페이지예요.
3단계: 실제로 무엇이 배포되었는지 확인하기
모든 경로가 404라면, 서버가 빌드된 앱을 제공하고 있지 않을 가능성이 커요.
- 올바른 폴더. 호스트는 프로젝트 루트가 아니라 빌드 결과물(Vite는 보통
dist, Create React App은build, 정적 Next.js 내보내기는out)을 게시해야 해요. - 빌드가 실행되었는지. 배포 로그를 읽으세요. 빌드가 실패했다면 호스트는 아무것도 배포하지 않았거나 오래된 버전을 배포했을 수 있어요.
index.html이 게시 폴더의 최상위에 있는지, 하위 폴더 안에 있지 않은지 확인하세요.- 기본 경로가 맞는지. 앱이
example.com/app/에서 제공된다면, 그 경로에 맞춰 빌드해야 해요(Vite의base, React Router의basename). 그렇지 않으면 모든 라우트와 자산이 빗나가요.
4단계: 누락된 파일과 잘못된 경로 고치기
페이지는 열리는데 자산이 404인 경우:
- 대소문자 구분. macOS와 Windows는 보통
Logo.png와logo.png를 같은 파일로 취급해요. 대부분의 리눅스 서버는 그렇지 않아요. 모든 참조가 실제 파일 이름과 정확히 일치하게 하세요. - 빌드 밖의 파일. Vite에서
public/안의 파일은 그대로 복사되어 루트에서 참조돼요(/logo.png). 코드에서 import한 파일은 번들에 포함돼요. 프로젝트 다른 곳에 있는 파일은 아예 배포되지 않을 수 있어요. - 상대 경로.
images/photo.jpg는 현재 URL을 기준으로 해석되므로/blog/post-1에서 깨져요./images/photo.jpg처럼 쓰세요. - 로컬 디스크에 저장된 업로드 파일. 사용자의 업로드가 서버의 자체 폴더에 기록되었다면, 새 버전이 배포될 때 사라져요. 제대로 된 파일 스토리지를 사용하세요.
5단계: 트레일링 슬래시를 일관되게 처리하기
/about과 /about/은 서로 다른 URL이에요. 호스트마다 슬래시를 붙이거나 제거하는 방식이 달라서, 링크나 라우터와 맞지 않으면 404나 리다이렉트 루프가 생길 수 있어요. 한 가지 방식을 고르고, 링크도 그 방식을 쓰게 하고, 호스트가 다른 형태를 그 방식으로 리다이렉트하도록 설정하세요. 검색엔진도 같은 페이지를 두 개로 보는 일을 막을 수 있어서 일관성이 도움이 돼요.
6단계: 404가 나는 API 경로 고치기
페이지는 열리지만 네트워크 탭에서 /api/... 요청이 404를 반환한다면:
- 백엔드가 배포되지 않았어요. 정적 호스트는 파일만 제공해요. 백엔드는 어딘가에서 실행되어야 해요.
- 운영 환경에서 경로가 다르게 라우팅돼요. 개발 중에는 프록시가
/api를 별도 서버로 전달했을 수 있어요. 운영에서는 호스트에 이와 동등한 리라이트가 필요하거나, 프런트엔드가 API의 실제 주소를 호출해야 해요. - SPA 폴백이 API 요청을 가로챘어요. API 요청이
index.html을 status 200으로 반환한다면 폴백 규칙이 너무 넓은 거예요./api/*는 폴백에서 제외하세요.
게시된 앱에서 /api/orders 요청이 404를 반환하는데, 미리보기에서는 잘 동작해요. 미리보기와 게시된 앱에서 /api가 어떻게 라우팅되는지 확인하고, 게시된 프런트엔드가 백엔드에 도달하는 방식을 똑같이 맞춰 주세요.
7단계: 옛 URL 리다이렉트하기
다른 사이트에서 옮겨 왔거나 구조를 바꾼 경우, 사람들과 검색엔진이 여전히 쓰는 옛 URL은 404가 돼요. 옛 URL 각각에서 새 위치로 301(영구) 리다이렉트를 추가하세요. 목록을 만들어 두고, 출시 후 샘플을 테스트하세요. 워드프레스 사이트를 옮기는 경우 그 목록을 만드는 방법은 워드프레스 사이트 이전하기 가이드를 참고하세요.
8단계: 도메인 확인하기
앱은 호스트 주소에서는 작동하는데 커스텀 도메인에서 404가 뜬다면, 도메인이 잘못된 프로젝트를 가리키거나, 호스트 쪽에 아직 사이트에 도메인이 연결되지 않았을 수 있어요. DNS 레코드와 호스트의 도메인 설정을 확인하세요. 커스텀 도메인 연결하는 법을 보세요.
실제 404 페이지를 유용하게 만들기
어떤 404는 정상이에요. 누군가 URL을 잘못 입력한 경우죠. 그 페이지를 도움이 되게 만드세요.
- 짧은 메시지와 홈페이지 및 주요 섹션으로 가는 링크
- 사이트에 검색 기능이 있다면 검색창
- 나머지 사이트와 같은 헤더와 내비게이션
설정이 허용하는 곳에서는 존재하지 않는 페이지에 실제 404 상태 코드를 반환하세요. 상태 200으로 반환된 “찾을 수 없음” 메시지는 검색엔진이 소프트 404라고 부르는 것이고, 크롤링 시간을 낭비하고 색인을 혼란스럽게 할 수 있어요.
흔한 실수
- 클릭으로만 테스트하기. 항상 깊은 URL을 새 탭에 붙여 넣고 새로 고침해서 테스트하세요.
- 모든 것을 잡는 폴백. 자산과 API 경로가
index.html을 반환해서 빈 페이지나 혼란스러운 API 오류가 생겨요. - 서버에서만 깨지는 대소문자 불일치.
- 소스 폴더를 배포하기. 빌드 결과물 대신요.
- 리다이렉트 없이 URL 구조 바꾸기. 모든 옛 링크가 404가 돼요.
Mythex의 404
Mythex에서는 워크스페이스에서 게시하고, 정적 프런트엔드는 정적 사이트로 배포돼요. 게시된 페이지가 404를 반환하면, 정확한 URL, 새로 고침에서 발생하는지 클릭에서 발생하는지, 네트워크 탭에 무엇이 보이는지를 에이전트에게 알려 주고 라우팅을 확인해 달라고 하세요. 라이브 앱은 다시 게시하기 전까지 스냅샷이므로, 수정 후에는 다시 게시하세요. 프로젝트를 최근에 바꿨는데 라이브 사이트가 예전 모습이면, 다시 게시하고 강력 새로 고침을 하세요. 빌드가 실패하면 게시 대화상자의 AI로 수정을 사용하세요. 자세한 내용은 문서의 게시하는 법과 게시 문제 해결, 그리고 AI로 만든 앱 디버깅하는 법 가이드를 참고하세요.
404 체크리스트
- 정확히 어떤 URL이 404인지, 클릭에서인지 새로 고침에서인지 기록함
- SPA 폴백이 앱 경로에 대해 status 200으로
index.html을 제공함 - 폴백이 실제 파일과
/api/*를 제외함 - 라우터에 “찾을 수 없음”을 보여 주는 캐치올 경로가 있음
- 빌드 결과물 폴더가 게시되고, 빌드가 성공함
- 기본 경로가 앱이 제공되는 위치와 일치함
- 파일 이름의 대소문자가 모든 참조와 일치함
- 자산 경로가
/로 시작함 - 트레일링 슬래시 방식이 일관됨
- API 경로가 운영 환경에 배포되고 라우팅됨
- 옛 URL에 301 리다이렉트가 있음
- 깊은 링크를 새 탭에 붙여 넣어 테스트함
자주 묻는 질문
React 앱에서 페이지를 새로 고치면 404가 뜨는 이유는 무엇인가요?
싱글 페이지 앱에서 /dashboard 같은 경로는 JavaScript 안에만 존재해요. 링크를 클릭하면 라우터가 브라우저에서 처리하니 잘 동작하지만, 새로 고침을 하면 서버에 /dashboard 파일을 요청하는데 그런 파일은 없어요. 해결책은 앱 경로에 대해 index.html을 응답하는 폴백 규칙을 두는 거예요.
Netlify, Vercel, Nginx에서 SPA 폴백은 어떻게 추가하나요?
Netlify에서는 /* /index.html 200이라고 적은 _redirects 파일을 추가하세요. Vercel에서는 모든 경로를 /index.html로 보내는 리라이트를 vercel.json에 추가하세요. Nginx에서는 location 블록 안에 try_files $uri $uri/ /index.html;을 쓰세요. 규칙은 바뀔 수 있으니 호스트의 문서를 확인하세요.
배포 후 이미지나 파일이 404가 뜨는 이유는 무엇인가요?
보통 파일이 빌드 결과물에 없거나, 경로의 대소문자가 맞지 않거나(서버는 노트북과 달리 대소문자를 구분하는 경우가 많아요), 슬래시로 시작해야 할 경로가 상대 경로로 되어 있기 때문이에요.
없는 페이지에서 실제 404 상태 코드를 반환해야 하나요?
네. 도움이 되는 찾을 수 없음 페이지를 보여 주되, 설정이 허용한다면 404 상태 코드를 반환하세요. 검색엔진은 ‘찾을 수 없음’ 메시지를 200 상태로 반환하면 소프트 404로 여기는데, 이는 크롤링 시간을 낭비하고 색인에 혼란을 줄 수 있어요.