Back to list
ReactNginxDeployVitepnpmSetup

[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 dev or pnpm dev running 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.

Related posts