[React][Setup] Build with npm/yarn/pnpm and Host on Nginx
Build a React (Vite) app, copy the static dist output to Nginx, and fix SPA routing with try_files so deep links do not 404.

1. Environment
- Local OS: Windows 11 / macOS (build environment)
- Server OS: Ubuntu Linux or similar (deploy environment)
- Web Server: Nginx
- Framework: React.js (Vite-based)
- Terminal: PowerShell (Windows) / zsh (macOS) / Bash (Linux)
2. Problem
After finishing a frontend project, everyone asks the same question:
“Can I just leave
npm run devorpnpm devrunning on the real server?”
The answer is “Absolutely not!” A Dev Server exists to hot-reload while you edit. It keeps the original source and heavy tracking features around. It is not built for production traffic or security.
3. Cause
There are two main reasons not to run development mode in production.
Performance and payload size
Unminified source is shipped to the browser, so load times get worse.
SPA (Single Page Application) limits
React keeps a single HTML entry (index.html) and swaps URLs for navigation. If a user opens https://mysite.com/mypage directly or hits refresh, Nginx looks for a real mypage file or folder and returns 404 Not Found.
4. Solution
1) Build the project with a package manager
When development is done, bundle and minify JavaScript, CSS, and images. Run one of these from the project root on your local machine.
Related: Create a React + Vite + TypeScript project
Windows / macOS (local)
With npm:
npm run build
With yarn:
yarn build
With pnpm (recommended):
pnpm build
After compilation, a new output folder appears at the project root.
- Vite projects create
./dist - Older CRA projects create
./build
dist/
"?" index.html # The single HTML entry point
"?" vite.svg # Favicon and other static assets
"?" assets/
"?" index-B2m9XkLp.js # Minified React bundle
"?" index-Cp4f_QxR.css # Minified styles
The random suffix like B2m9XkLp is a hash. Browsers cache downloaded files for speed. On redeploy, that hash must change so the browser fetches fresh code instead of an old cached screen.
2) Copy files to Nginx and configure routing
Copy everything inside dist to the web root on the Linux server (commonly /var/www/html).
In the Nginx config (/etc/nginx/sites-available/default or /etc/nginx/nginx.conf), use a block like this.
Nginx on a Linux production server
server {
listen 80;
server_name my-website.com; # Your domain or IP
root /var/www/html; # Where you copied the dist contents
index index.html;
location / {
# Core frontend deploy rule (prevents 404s)
# 1. Try the exact requested file ($uri)
# 2. Try a directory with that name ($uri/)
# 3. Otherwise always serve index.html so React Router can render
try_files $uri $uri/ /index.html;
}
}
Save the config and restart Nginx on the server to finish the deploy.
Linux server terminal
sudo systemctl restart nginx
5. Summary
Build the app into lightweight static files, put them on Nginx, and fix SPA routing. That is the standard production path.
“A built React app (dist) is just a pile of static files (HTML, JS, CSS).”
Once you understand that idea and Nginx try_files, you can design infrastructure cleanly for AWS S3 + CloudFront, Vercel, Netlify, and similar platforms.

