Sr Technical Writer
You can quickly deploy React applications to a server using the default Create React App build tool. The build
script compiles the application into a single directory containing all of the JavaScript code, images, styles, and HTML files. With the assets in a single location, you can deploy to a web server with minimal configuration.
In this comprehensive tutorial, you’ll deploy a React application on your local machine to an Ubuntu server running Nginx. You’ll build an application using Create React App, configure Nginx for optimal performance, set up reverse proxy configurations, implement SSL certificates, and learn production optimization techniques. We’ll also cover modern deployment strategies including CI/CD automation and containerization approaches that work across Ubuntu.
Before diving into the technical implementation, here are the essential concepts you’ll master:
This tutorial is compatible with Ubuntu 18.04, 20.04, 22.04, 24.04, and 25.04. If you are using Ubuntu version 16.04 or below, we recommend you upgrade to a more recent version since Ubuntu no longer provides support for these versions. You can refer to this official Ubuntu LTS Upgrade Guide for more information.
To follow this tutorial, you will need:
A server running Ubuntu, along with a non-root user with sudo
privileges and an active firewall. You can follow this Initial Server Setup Guide. To gain SSH access on a DigitalOcean Droplet, read through How to Connect to Droplets with SSH.
On your local machine, you will need a development environment running Node.js version 18 or higher. To install this on macOS or Ubuntu, follow the tutorial on How to Install Node.js on Ubuntu.
It is recommended that you also secure your server with an HTTPS certificate. You can do this with the How To Secure Nginx with Let’s Encrypt on Ubuntu tutorial.
A registered domain name or server IP.
If you use a domain name, you need to set up both of these DNS records for your server. If you are using DigitalOcean, please see our DNS documentation for details on how to add them.
your_domain
pointing to your server’s public IP address.www.your_domain
pointing to your server’s public IP address.You will also need a basic knowledge of JavaScript, HTML, and CSS, which you can find in our How To Build a Website With HTML series, How To Build a Website With CSS series, and in How To Code in JavaScript.
In this step, you’ll create an application using Create React App and build a deployable version of the boilerplate app with production optimizations.
To start, create a new application using Create React App in your local environment. In a terminal, run the command to build an application. In this tutorial, the project will be called react-deploy
:
- npx create-react-app react-deploy
The npx
command will run a Node package without downloading it to your machine. The create-react-app
script will install all of the dependencies needed for your React app and will build a base project in the react-deploy
directory. For more on Create React App, check out out the tutorial How To Set Up a React Project with Create React App.
Modern React Development: While Create React App is excellent for learning, consider using Vite for new projects as it offers faster build times and better development experience. For this tutorial, we’ll use Create React App for its simplicity and widespread adoption.
The code will run for a few minutes as it downloads and installs the dependencies. When it is complete, you will receive a success message. Your version may be slightly different if you use yarn
instead of npm
:
OutputSuccess! Created react-deploy at your_file_path/react-deploy
Inside that directory, you can run several commands:
npm start
Starts the development server.
npm build
Bundles the app into static files for production.
npm test
Starts the test runner.
npm eject
Removes this tool and copies build dependencies, configuration files
and scripts into the app directory. If you do this, you can’t go back!
We suggest that you begin by typing:
cd react-deploy
npm start
Happy hacking!
Following the suggestion in the output, first move into the project folder:
- cd react-deploy
Now that you have a base project, run it locally to test how it will appear on the server. Run the project using the npm start
script:
- npm start
When the command runs, you’ll receive output with the local server info:
OutputCompiled successfully!
You can now view react-deploy in the browser.
Local: `http://localhost:3000`
On Your Network: `http://192.168.1.110:3000`
Note that the development build is not optimized.
To create a production build, use npm build.
Open a browser and navigate to http://localhost:3000
. You will be able to access the boilerplate React app:
Stop the project by entering either CTRL+C
or ⌘+C
in a terminal.
Now that you have a project that runs successfully in a browser, you need to create a production build. Run the create-react-app
build script with the following:
- npm run build
This command will compile the JavaScript and assets into the build
directory. When the command finishes, you will receive some output with data about your build. Notice that the filenames include a hash, so your output will be slightly different:
OutputCreating an optimized production build...
Compiled successfully.
File sizes after gzip:
41.21 KB build/static/js/2.82f639e7.chunk.js
1.4 KB build/static/js/3.9fbaa076.chunk.js
1.17 KB build/static/js/runtime-main.1caef30b.js
593 B build/static/js/main.e8c17c7d.chunk.js
546 B build/static/css/main.ab7136cd.chunk.css
The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.
The build folder is ready to be deployed.
You may serve it with a static server:
serve -s build
Find out more about deployment here:
https://cra.link/deployment
The build
directory will now include compiled and minified versions of all the files you need for your project. At this point, you don’t need to worry about anything outside of the build
directory. All you need to do is deploy the directory to a server.
In this step, you created a new React application. You verified that the application runs locally and you built a production version using the Create React App build
script. In the next step, you’ll log onto your server to learn where to copy the build
directory.
In this step, you’ll start to deploy your React application to a server. But before you can upload the files, you’ll need to determine the correct file location on your deployment server. This tutorial uses Nginx as a web server, but the approach is the same with Apache (Ubuntu 22.04 / Ubuntu 20.04 / Ubuntu 18.04). The main difference is that the configuration files will be in a different directory.
To find the directory the web server will use as the root for your project, log in to your server using ssh
:
- ssh username@server_ip
Once on the server, look for your web server configuration in /etc/nginx/sites-enabled
. There is also a directory called sites-allowed
; this directory includes configurations that are not necessarily activated. Once you find the configuration file, display the output in your terminal with the following command:
- cat /etc/nginx/sites-enabled/your_domain
If your site has no HTTPS certificate, you will receive a result similar to this:
Outputserver {
listen 80;
listen [::]:80;
root /var/www/your_domain/html;
index index.html index.htm index.nginx-debian.html;
server_name your_domain www.your_domain;
location / {
try_files $uri $uri/ =404;
}
}
If you followed the Let’s Encrypt prerequisite to secure your Ubuntu server, you will receive this output:
Outputserver {
root /var/www/your_domain/html;
index index.html index.htm index.nginx-debian.html;
server_name your_domain www.your_domain;
location / {
try_files $uri $uri/ =404;
}
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/your_domain/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/your_domain/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = www.your_domain) {
return 301 https://$host$request_uri;
} # managed by Certbot
if ($host = your_domain) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name your_domain www.your_domain;
return 404; # managed by Certbot
}
In either case, the most important field for deploying your React app is root
. This points HTTP requests to the /var/www/your_domain/html
directory. That means you will copy your files to that location. In the next line, you can see that Nginx will look for an index.html
file. If you look in your local build
directory, you will see an index.html
file that will serve as the main entry point.
Log off the Ubuntu server and go back to your local development environment.
Now that you know the file location that Nginx will serve, you can upload your build.
scp
At this point, your build
files are ready to go. All you need to do is copy them to the server. A quick way to do this is to use scp
to copy your files to the correct location. The scp
command is a secure way to copy files to a remote server from a terminal. The command uses your ssh
key if it is configured. Otherwise, you will be prompted for a username and password.
The command format will be scp files_to_copy username@server_ip:path_on_server
. The first argument will be the files you want to copy. In this case, you are copying all of the files in the build
directory. The second argument is a combination of your credentials and the destination path. The destination path will be the same as the root
in your Nginx config: /var/www/your_domain/html
.
Copy all the build
files using the *
wildcard to /var/www/your_domain/html
:
- scp -r ./build/* username@server_ip:/var/www/your_domain/html
When you run the command, you will receive output showing that your files are uploaded. Your results will be slightly different:
Outputasset-manifest.json 100% 1092 22.0KB/s 00:00
favicon.ico 100% 3870 80.5KB/s 00:00
index.html 100% 3032 61.1KB/s 00:00
logo192.png 100% 5347 59.9KB/s 00:00
logo512.png 100% 9664 69.5KB/s 00:00
manifest.json 100% 492 10.4KB/s 00:00
robots.txt 100% 67 1.0KB/s 00:00
main.ab7136cd.chunk.css 100% 943 20.8KB/s 00:00
main.ab7136cd.chunk.css.map 100% 1490 31.2KB/s 00:00
runtime-main.1caef30b.js.map 100% 12KB 90.3KB/s 00:00
3.9fbaa076.chunk.js 100% 3561 67.2KB/s 00:00
2.82f639e7.chunk.js.map 100% 313KB 156.1KB/s 00:02
runtime-main.1caef30b.js 100% 2372 45.8KB/s 00:00
main.e8c17c7d.chunk.js.map 100% 2436 50.9KB/s 00:00
3.9fbaa076.chunk.js.map 100% 7690 146.7KB/s 00:00
2.82f639e7.chunk.js 100% 128KB 226.5KB/s 00:00
2.82f639e7.chunk.js.LICENSE.txt 100% 1043 21.6KB/s 00:00
main.e8c17c7d.chunk.js 100% 1045 21.7KB/s 00:00
logo.103b5fa1.svg 100% 2671 56.8KB/s 00:00
When the command completes, you are finished. Since a React project is built of static files that only need a browser, you don’t have to configure any further server-side language. Open a browser and navigate to your domain name. When you do, you will find your React project:
In this step, you deployed a React application to a server. You learned how to identify the root web directory on your server and you copied the files with scp
. When the files finished uploading, you were able to view your project in a web browser.
While your React app is now accessible, the default Nginx configuration isn’t optimized for production React applications. In this step, you’ll configure Nginx for better performance, security, and React Router compatibility.
React applications using client-side routing (React Router) require special Nginx configuration. When users navigate to routes like /about
or /contact
, the browser requests these paths from the server, but Nginx doesn’t know about these client-side routes and returns a 404 error.
The solution is to configure Nginx to serve the index.html
file for all routes that don’t correspond to actual files. This is called the “fallback” configuration.
Create a new Nginx configuration file specifically for your React application:
- sudo nano /etc/nginx/sites-available/your_domain
Replace the existing configuration with this optimized version:
server {
listen 80;
listen [::]:80;
server_name your_domain www.your_domain;
root /var/www/your_domain/html;
index index.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private must-revalidate auth;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/xml+rss
application/json;
# Static assets caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# React Router fallback - this is crucial for SPAs
location / {
try_files $uri $uri/ /index.html;
}
# API proxy (if you have a backend API)
location /api/ {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
In a nutshell, the configuration does the following:
try_files $uri $uri/ /index.html
: This is the React Router fallback. It first tries to serve the requested file, then the
directory, and finally falls back to index.html
for client-side routing./api/
requests to your backend server (if applicable).This Nginx configuration is specifically optimized for React applications. Let’s break down each section to understand what it does and why it’s important:
listen 80;
listen [::]:80;
server_name your_domain www.your_domain;
root /var/www/your_domain/html;
index index.html;
listen 80
) and IPv6 (listen [::]:80
) connections for maximum compatibilityyour-domain.com
and www.your-domain.com
from the same configurationindex.html
when no specific file is requestedadd_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
These headers protect against common web vulnerabilities:
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied expired no-cache no-store private must-revalidate auth;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/xml+rss
application/json;
This section provides significant performance improvements:
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
This configuration optimizes caching for static assets:
immutable
directive tells browsers the file won’t change (safe because React adds hashes to filenames like main.abc123.js
)access_log off
prevents logging of static asset requests, reducing I/O overheadlocation / {
try_files $uri $uri/ /index.html;
}
This is the most important configuration for React applications:
index.html
/about
or /contact
How it works:
$uri
)$uri/
)index.html
for client-side routinglocation /api/ {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
This section handles backend API integration:
/api/
requests to your backend server running on port 3001This optimized configuration provides significant improvements over basic Nginx setups:
The configuration is production-ready and handles the unique requirements of React Single Page Applications while providing enterprise-grade performance and security optimizations.
Test your Nginx configuration for syntax errors:
- sudo nginx -t
If the test passes, reload Nginx:
- sudo systemctl reload nginx
Many React applications need to communicate with backend APIs. Nginx can act as a reverse proxy, routing API requests to your backend server while serving the React app from the same domain.
If you have a Node.js/Express backend, here’s how to set it up:
- # Install PM2 for process management
- sudo npm install -g pm2
-
- # Create a simple Express server
- mkdir /var/www/api
- cd /var/www/api
- npm init -y
- npm install express cors
Create a simple API server:
// /var/www/api/server.js
const express = require('express');
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 3001;
app.use(cors());
app.use(express.json());
app.get('/api/health', (req, res) => {
res.json({ status: 'OK', timestamp: new Date().toISOString() });
});
app.get('/api/users', (req, res) => {
res.json([
{ id: 1, name: 'John Doe', email: 'john@example.com' },
{ id: 2, name: 'Jane Smith', email: 'jane@example.com' }
]);
});
app.listen(PORT, () => {
console.log(`API server running on port ${PORT}`);
});
Start the API server with PM2:
- pm2 start server.js --name "react-api"
- pm2 save
- pm2 startup
The Nginx configuration from Step 4 already includes the API proxy. The /api/
location block routes requests to your backend server running on port 3001.
SSL certificates are essential for production applications. Let’s set up Let’s Encrypt certificates for your domain.
- sudo apt update
- sudo apt install certbot python3-certbot-nginx
- sudo certbot --nginx -d your_domain -d www.your_domain
Certbot will automatically update your Nginx configuration to include SSL settings and redirect HTTP to HTTPS.
After installation, your Nginx configuration will include SSL settings:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name your_domain www.your_domain;
ssl_certificate /etc/letsencrypt/live/your_domain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your_domain/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Your existing configuration...
}
server {
listen 80;
listen [::]:80;
server_name your_domain www.your_domain;
return 301 https://$server_name$request_uri;
}
- sudo crontab -e
Add this line to renew certificates automatically:
0 12 * * * /usr/bin/certbot renew --quiet
Create a performance-optimized Nginx configuration by editing the main configuration file:
# /etc/nginx/nginx.conf
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 4096;
use epoll;
multi_accept on;
}
http {
# Basic settings
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Buffer sizes
client_body_buffer_size 128k;
client_max_body_size 10m;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
# Timeouts
client_body_timeout 12;
client_header_timeout 12;
keepalive_timeout 15;
send_timeout 10;
# Gzip settings
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/xml+rss
application/json
application/atom+xml
image/svg+xml;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# Include your site configuration
include /etc/nginx/sites-enabled/*;
}
This configuration optimizes Nginx at the system level for high-performance React application serving. Let’s break down each section:
worker_processes auto;
worker_rlimit_nofile 65535;
worker_processes auto
: Automatically sets the number of worker processes to match your CPU cores, maximizing parallel processingworker_rlimit_nofile 65535
: Increases the file descriptor limit per worker, allowing more concurrent connections (default is usually 1024)events {
worker_connections 4096;
use epoll;
multi_accept on;
}
worker_connections 4096
: Each worker can handle up to 4,096 simultaneous connections (total = workers × 4096)use epoll
: Uses the most efficient event processing method on Linux systemsmulti_accept on
: Allows workers to accept multiple connections at once, reducing context switchingsendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
sendfile on
: Uses the kernel’s sendfile() system call for efficient file serving, bypassing user-space copyingtcp_nopush on
: Optimizes TCP packet transmission by waiting to send packets until they’re fulltcp_nodelay on
: Sends small packets immediately for better real-time performancekeepalive_timeout 65
: Keeps connections alive for 65 seconds, reducing connection overheadtypes_hash_max_size 2048
: Increases hash table size for faster MIME type lookupsclient_body_buffer_size 128k;
client_max_body_size 10m;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
client_body_buffer_size 128k
: Allocates 128KB buffer for request bodies, reducing disk I/O for small uploadsclient_max_body_size 10m
: Allows file uploads up to 10MB (adjust based on your React app’s needs)client_header_buffer_size 1k
: Standard buffer for HTTP headerslarge_client_header_buffers 4 4k
: Handles large headers (cookies, custom headers) with 4 buffers of 4KB eachclient_body_timeout 12;
client_header_timeout 12;
keepalive_timeout 15;
send_timeout 10;
client_body_timeout 12
: Gives clients 12 seconds to send request body dataclient_header_timeout 12
: Gives clients 12 seconds to send complete headerskeepalive_timeout 15
: Keeps idle connections alive for 15 secondssend_timeout 10
: Gives 10 seconds to send response data to clientsgzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_proxied any;
gzip_comp_level 6;
gzip_types
text/plain
text/css
text/xml
text/javascript
application/javascript
application/xml+rss
application/json
application/atom+xml
image/svg+xml;
gzip_comp_level 6
: Balances compression ratio (6) with CPU usage (1-9 scale)gzip_proxied any
: Compresses responses for all proxy requestsgzip_vary on
: Adds “Vary: Accept-Encoding” header to help caches understand compressionlimit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
This advanced configuration provides:
To apply these changes:
- sudo nano /etc/nginx/nginx.conf
Replace the existing configuration with the optimized version above, then test and reload:
- sudo nginx -t
- sudo systemctl reload nginx
Note: These settings are optimized for production environments. For development, you may want to reduce some values to conserve resources.
Optimize your React build for production:
// package.json
{
"scripts": {
"build": "GENERATE_SOURCEMAP=false npm run build",
"build:analyze": "npm run build && npx serve -s build"
}
}
Create environment-specific builds:
# .env.production
REACT_APP_API_URL=https://your-domain.com/api
REACT_APP_ENVIRONMENT=production
Create .github/workflows/deploy.yml
:
name: Deploy React App to Ubuntu
on:
push:
branches: [ main ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test -- --coverage --watchAll=false
- name: Build React app
run: npm run build
env:
REACT_APP_API_URL: ${{ secrets.REACT_APP_API_URL }}
- name: Deploy to server
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
cd /var/www/your-domain/html
rm -rf *
# Files will be uploaded via rsync
- name: Upload files
run: |
rsync -avz --delete ./build/ ${{ secrets.USERNAME }}@${{ secrets.HOST }}:/var/www/your-domain/html/
- name: Restart Nginx
uses: appleboy/ssh-action@v0.1.5
with:
host: ${{ secrets.HOST }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_KEY }}
script: |
sudo systemctl reload nginx
This automated deployment pipeline streamlines your workflow by automatically building, testing, and deploying your React application whenever you push changes to the main branch. Here’s how the process works, step by step:
npm ci
for reliable and reproducible builds.rsync
to efficiently upload only changed files, minimizing transfer time and bandwidth usage.Configure Nginx logging for better debugging:
# In your site configuration
access_log /var/log/nginx/react-app.access.log;
error_log /var/log/nginx/react-app.error.log;
# Log format for detailed analysis
log_format detailed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
- # Install PM2 monitoring
- pm2 install pm2-logrotate
- pm2 monit
Add a health check to your React app:
// In your React app
const HealthCheck = () => {
const [status, setStatus] = useState('checking');
useEffect(() => {
fetch('/api/health')
.then(res => res.json())
.then(data => setStatus(data.status))
.catch(() => setStatus('error'));
}, []);
return <div>API Status: {status}</div>;
};
Install and configure monitoring tools:
- # Install htop for system monitoring
- sudo apt install htop
-
- # Monitor Nginx status
- sudo systemctl status nginx
-
- # Check disk usage
- df -h
-
- # Monitor memory usage
- free -h
Deploying a React app with Nginx on Ubuntu involves several key steps:
npm run build
to create optimized production filestry_files $uri $uri/ /index.html
/var/www/your-domain/html
)The critical configuration for React Router compatibility is the try_files
directive, which ensures all client-side routes fall back to index.html
for proper single-page application functionality.
React build files should be placed in your Nginx server’s document root directory. The standard location is /var/www/your-domain/html/
on Ubuntu systems.
Here’s the typical structure:
/var/www/your-domain/html/
├── index.html # Main entry point
├── static/
│ ├── css/ # Compiled CSS files
│ ├── js/ # Compiled JavaScript files
│ └── media/ # Images and other assets
├── manifest.json # PWA manifest
└── robots.txt # SEO robots file
Ensure your Nginx configuration points to this directory with the root
directive and that the web server has proper read permissions.
React Router routes fail with Nginx because the server doesn’t know about client-side routes. When users navigate to /about
or /contact
, Nginx tries to find these files and returns 404 errors.
Solution: Configure Nginx with a fallback to index.html
:
location / {
try_files $uri $uri/ /index.html;
}
This configuration tells Nginx to:
$uri
)$uri/
)index.html
for client-side routingAdditionally, ensure your React app uses BrowserRouter
instead of HashRouter
for clean URLs without hash fragments.
Yes, you can deploy both frontend and backend on the same Nginx server using reverse proxy configuration. This is a common and efficient setup for full-stack applications.
Configuration example:
server {
listen 80;
server_name your-domain.com;
# Serve React app for all routes except API
location / {
root /var/www/your-domain/html;
try_files $uri $uri/ /index.html;
}
# Proxy API requests to backend
location /api/ {
proxy_pass http://localhost:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
This setup allows your React app to make API calls to /api/endpoint
which get proxied to your backend server running on port 3001.
Enable HTTPS for your React app using Let’s Encrypt free SSL certificates:
Install Certbot:
sudo apt update
sudo apt install certbot python3-certbot-nginx
Obtain SSL certificate:
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
Verify automatic renewal:
sudo certbot renew --dry-run
Certbot automatically configures Nginx with:
Your React app will then be accessible via https://your-domain.com
with a valid SSL certificate.
Optimize React static asset caching with these Nginx configurations:
Long-term caching for static assets:
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
Cache busting for updated files:
React’s build process automatically adds hashes to filenames (e.g., main.abc123.js
), so you can safely cache these files for long periods.
Gzip compression:
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json;
Browser caching for HTML:
location ~* \.html$ {
expires 1h;
add_header Cache-Control "public, must-revalidate";
}
This configuration reduces bandwidth usage by 60-80% and significantly improves page load times for returning visitors.
You’ve successfully deployed a production-ready React application with Nginx on Ubuntu, incorporating enterprise-grade optimizations, robust security configurations, and comprehensive monitoring capabilities.
This approach ensures optimized performance through features like gzip compression, static asset caching, and advanced Nginx tuning for faster load times. Security is strengthened with SSL certificates, security headers, and proper access controls, while scalability is achieved via a reverse proxy setup that supports API integration and microservices.
The techniques outlined in this tutorial are compatible with the latest Ubuntu versions, ensuring your deployment remains reliable and future-proof across current and upcoming Ubuntu LTS releases.
Recommended Tutorials to Continue Learning:
If you would like to read more React tutorials, check out our React Topic page, or return to the How To Code in React.js series page.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
React is a popular JavaScript framework for creating front-end applications, such as user interfaces that allow users to interact with programs. Originally created by Facebook, it has gained popularity by allowing developers to create fast applications using an intuitive programming paradigm that ties JavaScript with an HTML-like syntax known as JSX.
In this series, you will build out examples of React projects to gain an understanding of this framework, giving you the knowledge you need to pursue front-end web development or start out on your way to full stack development.
Browse Series: 21 tutorials
I help Businesses scale with AI x SEO x (authentic) Content that revives traffic and keeps leads flowing | 3,000,000+ Average monthly readers on Medium | Sr Technical Writer @ DigitalOcean | Ex-Cloud Consultant @ AMEX | Ex-Site Reliability Engineer(DevOps)@Nutanix
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
The quit command doesn’t work on MacOS, since abort task on macOS is still ctrl+c
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
Sign up and get $200 in credit for your first 60 days with DigitalOcean.*
*This promotional offer applies to new accounts only.