React, Vue, Angular와 같은 SPA(Single Page Application)를 빌드하여 Nginx 웹 서버에 배포한 후 가장 빈번하게 마주치는 문제가 있습니다. 바로 루트(/)에서는 페이지 이동이 잘 되는데, 특정 하위 경로(예: /mypage, /dashboard)에서 새로고침(F5)을 누르거나 주소를 직접 입력하면 404 Not Found 오류가 발생하는 현상입니다.
이 글에서는 이 문제가 발생하는 근본적인 원인과 Nginx의 try_files 지시어를 활용해 완벽하게 해결하는 방법을 정리합니다.
1. 문제 상황 및 증상
웹 애플리케이션을 빌드한 결과물(정적 파일)을 /var/www/html 또는 별도의 디렉토리에 두고 Nginx 기본 설정으로 서빙하는 경우를 예로 들어보겠습니다.
- 메인 페이지(
https://example.com/) 접속: 정상 동작 - 메인 화면에서 버튼을 클릭해
/profile페이지로 이동: 정상 동작 (URL도 바뀜) - 그 상태에서 새로고침(F5) 클릭 또는 새 브라우저 창에서
https://example.com/profile직접 입력:404 Not Found오류 발생
2. 발생 원인: CSR(클라이언트 사이드 라우팅)의 동작 원리
이 현상은 Nginx의 버그가 아니라, SPA의 동작 방식과 전통적인 웹 서버의 파일 탐색 방식 간의 차이 때문에 발생합니다.
-
SPA(React Router, Vue Router)의 라우팅:
- SPA는 단 하나의
index.html파일과 JavaScript 번들 파일로 구성됩니다. - 브라우저 주소창의 경로(
/profile)는 실제로 서버에 존재하는 물리적 파일이나 폴더가 아니라, 브라우저의 JavaScript 라우터가 가상으로 처리하는 경로입니다. - 메인 페이지에서 버튼을 클릭해 이동할 때는 브라우저가 서버에 새 요청을 보내지 않고 HTML5 History API(
pushState)를 이용해 클라이언트 내부에서 화면을 전환합니다.
- SPA는 단 하나의
-
Nginx의 기본 동작:
- 그러나 사용자가 주소창에 직접
https://example.com/profile을 치거나 새로고침을 누르면, 브라우저는 Nginx 웹 서버에 직접/profile이라는 리소스를 달라고 HTTP GET 요청을 보냅니다. - Nginx는 서버의 루트 디렉토리에서
profile이라는 파일이나 디렉토리를 찾으려고 시도합니다. - 하지만 서버에는
index.html만 존재하고profile이라는 물리적 파일은 없기 때문에, Nginx는 당연히 404 Not Found를 응답하게 됩니다.
- 그러나 사용자가 주소창에 직접
3. 해결 방법: try_files 지시어 설정
이 문제를 해결하려면 Nginx에게 다음과 같이 명령해야 합니다:
“요청한 경로에 실제 파일이나 디렉토리가 있다면 그것을 서빙하고, 만약 없다면 무조건
index.html을 반환해서 브라우저의 프론트엔드 라우터가 처리하게 해라!”
이를 가능하게 해주는 것이 바로 Nginx의 try_files 지시어입니다.
3.1 기본 Nginx 설정 (nginx.conf 또는 가상 호스트 파일)
서버 블록(server) 내부의 location / 블록을 다음과 같이 수정합니다.
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.html;
location / {
# 1) 요청한 파일($uri)이 존재하는지 확인
# 2) 요청한 디렉토리($uri/)가 존재하는지 확인
# 3) 둘 다 없으면 마지막 대체재로 /index.html을 반환!
try_files $uri $uri/ /index.html;
}
# 정적 에셋 캐싱 설정 (선택 사항)
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, no-transform";
}
}
3.2 try_files $uri $uri/ /index.html; 동작 흐름
$uri: 예를 들어https://example.com/main.js요청이 오면 실제 파일인main.js를 바로 전달합니다.$uri/: 디렉토리 요청이 오면 해당 디렉토리의index.html을 확인합니다./index.html: 만약https://example.com/profile요청이 들어와서$uri도 없고$uri/도 없다면, 404를 내는 대신 **루트의/index.html을 클라이언트에게 정상 응답(HTTP 200)**으로 내려줍니다.- 브라우저는
index.html과 JS 번들을 로드한 뒤, 현재 주소창의/profile경로를 읽어 알맞은 React/Vue 컴포넌트를 화면에 렌더링합니다.
4. 서브 경로(Sub-path)에 SPA가 배포된 경우
만약 메인 도메인의 하위 경로(예: https://example.com/admin/)에 별도의 SPA 앱이 배포되어 있다면 다음과 같이 설정해야 합니다.
location /admin/ {
alias /var/www/admin-app/;
index index.html;
try_files $uri $uri/ /admin/index.html;
}
5. 설정 적용 및 테스트
설정 파일을 수정한 후에는 반드시 Nginx 설정 문법을 검사하고 서비스를 재로드해야 안전합니다.
# 1. 설정 파일 문법 검사
sudo nginx -t
# 2. 검사 결과(syntax is ok) 확인 후 무중단 재로드
sudo systemctl reload nginx
6. 요약
- SPA의 새로고침 404 에러는 물리적 파일이 없는 가상 경로를 Nginx가 서버에서 찾으려고 하기 때문에 발생합니다.
location /내에try_files $uri $uri/ /index.html;구문을 추가하면 해결됩니다.- 이 설정을 통해 새로고침이나 직접 URL 접속 시에도 항상
index.html이 전달되어 프론트엔드 클라이언트 라우터가 정상 동작하게 됩니다.