
SPA 배포 후 새로고침하면 404가 뜨는 이유 (Nginx, S3, Netlify 설정법)
React나 Vue 프로젝트를 빌드해서 배포했는데, 홈 화면은 잘 나오지만 새로고침만 하면 'Page Not Found'가 뜨나요? CSR의 원리와 서버 설정(Nginx, Apache, S3)을 통해 이를 해결하는 완벽 가이드.

React나 Vue 프로젝트를 빌드해서 배포했는데, 홈 화면은 잘 나오지만 새로고침만 하면 'Page Not Found'가 뜨나요? CSR의 원리와 서버 설정(Nginx, Apache, S3)을 통해 이를 해결하는 완벽 가이드.
프론트엔드 개발자가 알아야 할 4가지 저장소의 차이점과 보안 이슈(XSS, CSRF), 그리고 언제 무엇을 써야 하는지에 대한 명확한 기준.

매번 3-Way Handshake 하느라 지쳤나요? 한 번 맺은 인연(TCP 연결)을 소중히 유지하는 법. HTTP 최적화의 기본.

클래스 이름 짓기 지치셨나요? HTML 안에 CSS를 직접 쓰는 기괴한 방식이 왜 전 세계 프론트엔드 표준이 되었는지 파헤쳐봤습니다.

HTTP는 무전기(오버) 방식이지만, 웹소켓은 전화기(여보세요)입니다. 채팅과 주식 차트가 실시간으로 움직이는 기술적 비밀.

프론트엔드 개발자가 흔히 겪는 공포의 순간이 있습니다.
npm run dev로 로컬에서 개발할 때는 아무 문제가 없습니다. 메뉴를 클릭해도 페이지가 잘 바뀌고, 뒤로 가기도 잘 되고, 특정 페이지(/about)에서 새로고침을 해도 아주 잘 뜹니다.
"완벽해!" 하고 빌드를 합니다(npm run build).
그리고 결과물(dist 폴더)을 AWS S3나 Nginx 서버에 올립니다.
접속해보니 메인 페이지(index.html)는 잘 나옵니다.
그런데 상단 메뉴를 눌러서 /mypage로 이동한 뒤, 새로고침(F5)을 누르는 순간...
404 Not Found 하얀 화면에 차가운 에러 메시지만 덩그러니 뜹니다.
이건 버그가 아닙니다. SPA(Single Page Application)의 작동 원리를 이해하지 못해서 생긴 서버 설정(Server Configuration) 문제입니다. 코드를 100번 수정해도 해결되지 않습니다. 범인은 코드가 아니라 서버 설정이니까요.
이 현상을 이해하려면 전통적인 웹(MPA)과 현대적인 웹(SPA)의 라우팅 차이를 알아야 합니다.
과거의 웹사이트는 파일 기반이었습니다. PHP, JSP, ASP가 이에 해당합니다.
example.com/about 요청 -> 서버 하드 디스크에 있는 about.html 또는 about.php 파일을 찾아서 줍니다.example.com/contact 요청 -> contact.html 파일을 찾아서 줍니다.React, Vue, Angular는 Single Page, 즉 페이지가 index.html 하나뿐입니다.
example.com에 접속 -> 서버는 index.html과 bundle.js를 줍니다./about을 클릭 -> 브라우저의 주소창만 자바스크립트(history.pushState)로 바꿉니다./about에 있는지 /contact에 있는지 알 길이 없습니다.사용자가 /about 페이지를 보고 있다가 새로고침을 누릅니다.
새로고침은 "브라우저야, 지금 주소창에 있는 example.com/about 페이지를 서버에서 다시 받아와!"라는 명령입니다.
요청을 받은 서버(Nginx, S3 등)는 당황합니다.
"어? 내 하드 디스크에는 index.html밖에 없는데? about이라는 파일이나 폴더는 눈 씻고 찾아봐도 없는데?"
그래서 정직하게 404 Not Found를 돌려주는 것입니다.
해결 방법은 간단합니다. 서버에게 거짓말을 시키는 것입니다.
"사용자가 어떤 주소(/about, /contact, /user/123)를 요청하든 간에, 파일이 없으면 404 에러 대신 무조건 index.html을 내려줘!"
그러면 브라우저는 일단 index.html을 받고, 그 안에 포함된 React/Vue 자바스크립트가 실행됩니다.
자바스크립트 라우터(React Router, Vue Router)가 깨어나서 주소창(window.location.pathname)을 확인합니다.
"어? 현재 주소가 /about이네? 그렇다면 나는 About 컴포넌트를 렌더링해야겠다!"
이렇게 해서 정상적으로 화면이 뜨게 되는 것입니다.
이것을 서버 설정 용어로 Rewrite 또는 Fallback이라고 합니다.
여러분이 쓰는 배포 환경에 딱 맞는 설정을 복사해서 쓰세요.
실제로 가장 많이 쓰는 웹 서버입니다. nginx.conf 또는 default.conf의 location / 블록을 수정합니다.
server {
listen 80;
server_name example.com;
root /usr/share/nginx/html; # 빌드 파일 경로
index index.html;
location / {
# 1. 파일($uri)이 있으면 그걸 줌 (이미지, JS, CSS 등)
# 2. 없으면 폴더($uri/)를 찾아봄
# 3. 그래도 없으면 /index.html을 줌 (Fallback)
try_files $uri $uri/ /index.html;
}
}
try_files $uri $uri/ /index.html; 이 한 줄이 핵심입니다.
S3만 쓸 때와 CloudFront(CDN)를 붙일 때가 다릅니다. (HTTPS 때문에 보통 CloudFront를 같이 씁니다).
S3 정적 웹 사이트 호스팅:
index.html로 설정합니다.index.html이 나갑니다.)CloudFront:
/index.htmlGitHub Pages는 기본적으로 SPA 리다이렉트를 지원하지 않습니다. 그래서 꼼수(Hack)를 써야 합니다.
404.html 파일을 만듭니다.index.html의 내용을 그대로 복사해서 404.html에 붙여넣습니다.404.html(=index.html)이 실행되면서 React/Vue가 구동됩니다.이 서비스들은 SPA를 위해 태어났기 때문에 설정이 매우 쉽습니다.
public/_redirects 파일 생성
/* /index.html 200
vercel.json 설정
{
"rewrites": [{ "source": "/:path*", "destination": "/index.html" }]
}
프로젝트 루트 폴더에 .htaccess 파일을 만들고 다음 내용을 붙여넣습니다.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
서버 설정을 건드릴 권한이 없거나 너무 귀찮다면, Hash Router를 쓰는 방법도 있습니다.
주소에 샵(#)을 붙이는 방식입니다.
example.com/#/about
브라우저는 # 뒤에 오는 부분(Fragment)은 서버에 보내지 않습니다.
즉, 사용자가 example.com/#/about을 요청해도 서버는 example.com만 봅니다.
그래서 무조건 index.html을 받아오게 됩니다.
하지만 URL이 못생겨지고, 검색 엔진 최적화(SEO)에 불리하기 때문에 실제로는 거의 쓰지 않습니다. (관리자 페이지 같은 내부용 툴에는 괜찮습니다).
Next.js나 Nuxt.js 같은 Server-Side Rendering (SSR) 프레임워크를 쓰면 이 고생을 안 해도 됩니다.
SSR은 서버(Node.js)가 돌아가고 있습니다.
사용자가 /about을 요청하면, Node.js 서버가 받아서 "아, About 페이지 달라는 거구나" 하고 그 자리에서 HTML을 만들어서(Render) 줍니다.
즉, 모든 URL에 대해 진짜 파일(HTML)을 주는 것과 같은 효과를 냅니다.
하지만 정적 사이트(Static Site)로 배포하는 경우(Next.js의 output: export)에는 React와 똑같은 404 문제가 발생하므로, 위에서 설명한 try_files 설정이 필요합니다.
배포 후 404 에러는 여러분의 코드가 잘못된 게 아닙니다. "없는 파일을 요청하면 index.html을 줘라"라는 규칙을 서버에 알려주지 않았을 뿐입니다.
index.html로 돌려보내는 Fallback 설정 추가.try_files), S3(Error Document), Apache(.htaccess).이제 자신 있게 배포하세요!