This website uses cookies

Our website, platform and/or any sub domains use cookies to understand how you use our services, and to improve both your experience and our marketing relevance.

Go hands-off. Autoscale your WordPress site on Cloudways Autonomous.Start Your Free Trial

How to Deploy a React App to Cloudways (Complete Guide)

Updated on August 7, 2026

10 Min Read
Illustration of a dark terminal window showing npm build and server commands, with a small code snippet card and a green rocket badge on a blue gradient background.

Key Takeaways

  • React apps must be built into static files before deployment — browsers can’t run JSX or unminified code directly.
  • A custom Express server with a catch-all route prevents 404 errors when users refresh on client-side routes like /dashboard.
  • Cloudways Velocity’s Express preset runs a persistent Node.js process, avoiding the cold starts and routing issues of static or serverless hosting.
  • Deploying involves pushing your project to GitHub, selecting the Express preset on Cloudways, and setting the entry file to server.js.

Building a React app locally is the fun part. You get instant hot-reloading. Error overlays pop up instantly. Everything just works.

Pushing that code live is a totally different story. You always hit a wall.

Run npm run build on a normal Vite project. Drop those files onto a standard web host. See what happens. It rarely works right out of the box. You get a blank page. Or worse. Everything works fine until a user hits refresh. Then the server throws a massive 404 error.

It sucks.

I won’t bore you with web server lectures. Let’s just talk about getting your app live fast. Zero routing errors.

My goal is simple. I want to build a clean React dashboard and push it live to a persistent Cloudways Node.js environment. No serverless cold starts. No broken links. Just seamless deployment.

The Difference Between Development and Production in React

Running a local development server gives you a highly specialized environment. It builds your JSX and CSS on the fly. It watches your files. You hit save. It injects those changes directly into the browser.

Browsers don’t understand React out of the box. They can’t read your components. They can’t read JSX. They only understand raw HTML, CSS, and plain JavaScript.

Because of this, your app needs a build step. Here is how the two environments compare:

Environment Focus Local Development Live Production
Code Processing Compiles JSX and CSS on the fly. Pre-compiled via a build script.
File Delivery Injects heavy code for hot-reloading. Serves highly minified static files.
Server Role Actively watches and transforms files. Just hands plain files to the browser.

This build process strips out the development tools. The bundler takes your entire React tree. It resolves all the imports. It compiles everything down into highly minified static files.

In production, you aren’t actually running React. The heavy lifting is done. Your server simply hands those pre-compiled files over to the user’s browser. The browser downloads the JavaScript. It parses it. It brings your interface to life.

Why Deploy React on a Node.js Server?

A built React app is essentially just a folder full of static files. You might be wondering why I am deploying it on a Node.js server instead of a basic static host.

Standard static hosting works fine for simple landing pages. But it creates a massive bottleneck for Single Page Applications.

The biggest issue is client-side routing.

React Router intercepts clicks. It changes the URL in the browser without making a network request to the server. But here is the catch. If a user navigates to [yourdomain.com/dashboard](https://yourdomain.com/dashboard) and hits refresh, the browser makes a hard request. It looks for a physical file named dashboard.html.

A basic static server looks in your folder. It fails to find that file. It throws a 404 Not Found error.

Running a lightweight Express server gives you complete control over this logic. You can write a catch-all route. When the server gets a request for a file it can’t find, it falls back and serves index.html. React Router takes back control. It reads the URL and loads the correct component.

Plus, having a dedicated Node runtime lets you proxy API requests later to bypass strict CORS limits.

Mini Project: Build a Crypto Market Dashboard

I’m going to build a fast, client-rendered product showcase. I’ll write a custom Express backend to serve the files. Then a React frontend that fetches live pricing data.

Scaffolding the Project with Vite and Tailwind

First things first. I need to set up my local workspace.

I’m actually writing this from my office laptop today. The IT department locked this machine down completely. I don’t have admin rights. I can’t run standard installers. Because of this, I had to download the standalone binary .zip version of Node.js.

Node.js standalone Windows binary download page in browser

Command Prompt has no idea where Node lives here. Before I can even scaffold the project, I have to tell CMD where my Node folder is.

I will open Command Prompt and run this command:

set PATH=%PATH%;C:\Users\abdulrehman\Downloads\node-v24.18.0-win-x64\node-v24.18.0-win-x64

It works perfectly. CMD now executes Node and npm commands. I’ll change directories to my desktop and spin up the new project.

For years, everyone used create react app. Do not do that. It is bloated. It is slow. It is officially deprecated. I use Vite instead. It is lightning fast.

cd C:\Users\abdulrehman\Desktop
npm create vite@latest crypto-dashboard -- --template react

Vite React project scaffolding command output in terminal

Vite project created successfully confirmation in terminal

Next up, I will move into my project directory and install dependencies. I need the standard React packages. I also need Tailwind CSS to style the dashboard quickly.

cd crypto-dashboard

Terminal showing directory change into crypto dashboard folder

npm install

npm install command output installing React dependencies

npm install tailwindcss @tailwindcss/vite

Terminal output installing Tailwind CSS and Vite plugin packages

I will open the folder in VS Code. I need to register the Tailwind plugin inside Vite. I’ll open vite.config.js and add @tailwindcss/vite to the plugins array:

VS Code file explorer showing Vite React project structure

VS Code editor with vite.config.js file open

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
  ],
});

Then, I’ll replace the contents of src/index.css with the Tailwind import:

@import "tailwindcss";

body {
  @apply bg-gray-900 text-white;
}

Fetching Live Market Data

A dashboard needs data. I will use the free CoinGecko API. It grabs cryptocurrency images, prices, and tickers.

I’ll open src/App.jsx. Clear out all the default Vite counter code. Drop in my React component.

import { useState, useEffect } from 'react';

export default function App() {
  const [coins, setCoins] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=8&page=1&sparkline=false')
      .then(res => res.json())
      .then(data => {
        setCoins(data);
        setLoading(false);
      })
      .catch(err => console.error("Failed to fetch data:", err));
  }, []);

  return (
    <div className="max-w-5xl mx-auto p-8 font-sans">
      <header className="mb-10 text-center">
        <h1 className="text-4xl font-bold text-blue-400">Live Crypto Markets</h1>
        <p className="text-gray-400 mt-2">Real-time pricing dashboard</p>
      </header>

      {loading ? (
        <p className="text-center text-gray-400 animate-pulse text-xl">Fetching live data...</p>
      ) : (
        <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
          {coins.map(coin => (
            <div key={coin.id} className="bg-gray-800 p-6 rounded-xl shadow-lg border border-gray-700 flex flex-col items-center hover:scale-105 transition-transform duration-200">
              <img src={coin.image} alt={coin.name} className="w-16 h-16 mb-4 object-contain" />
              <h2 className="text-xl font-bold mt-2">{coin.name}</h2>
              <span className="text-gray-500 uppercase text-sm font-semibold tracking-wider mb-4">{coin.symbol}</span>
              <p className="text-2xl font-mono text-green-400 font-bold">${coin.current_price.toLocaleString()}</p>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

VS Code editor displaying App.jsx React component code

The logic is straightforward. The useEffect hook fires off a network request the moment the component mounts. It stores the JSON response in the coins state. That flips the loading toggle and renders the grid.

Writing the Express Server

Now for the fun part. I need to build the actual web server. It will host this React app in production.

First, I install Express:

npm install express

Terminal output after installing Express package with npm

I’ll create a file named server.js in the root of my project directory. This script serves the compiled static files. It also handles the client-side routing fallback.

import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';

// Node.js ES modules do not have __dirname built-in, so we reconstruct it.
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const app = express();
const PORT = process.env.PORT || 3000;

// Serve the static files out of the Vite build folder
app.use(express.static(path.join(__dirname, 'dist')));

// Catch-all route to hand off routing entirely to React
app.get('/{*splat}', (req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

// Bind to 0.0.0.0 so Cloudways' NGINX proxy can route incoming traffic
app.listen(PORT, '0.0.0.0', () => {
  console.log(`Server listening on port ${PORT}`);
});

VS Code editor showing server.js Express server code

That app.get(‘/{*splat}’) block is incredibly important. Say a user refreshes the page on /portfolio. Express will not find a portfolio.html file in the dist folder. Instead of throwing a 404, this catch-all route steps in. It sends back index.html. React loads up. It displays the correct route.

Configuring package.json for the Build

My backend script relies on top-level import statements. Node needs “type”: “module” enabled inside package.json to parse the files correctly.

I will open my package.json file. The Vite template already generated a few default scripts. I just need to add a start command so the server knows how to boot up in production.

{
  "name": "crypto-dashboard",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "lint": "oxlint",
    "preview": "vite preview",
    "start": "node server.js"
  }
}

VS Code package.json file with start script for production

My Express server is ready. I’ll run npm run build followed by npm start. My terminal will now show the server running.

Terminal running npm run build command for production files

Terminal showing Express server listening on port after npm start

And if I open http://localhost:3000 in my browser, the app loads beautifully.

Crypto dashboard React app loaded in browser at localhost

Deploying the React App to Cloudways Velocity

My code is ready. Now I need a place to host it. I’m deploying this on Cloudways Managed Node.js hosting (Velocity). This completely prevents cold starts. It gives me a persistent Node process running 24/7.

Pushing the Code to GitHub

Cloudways pulls its code directly from Git. I’ll initialize a repository locally. Commit my files. Push them to a new repository on my GitHub account.

Creating a new GitHub repository for the crypto dashboard project

git init
git add .
git commit -m "Initial commit: Crypto Dashboard ready for production"
git branch -M main
git remote add origin https://github.com/abdulrehman293/crypto-dashboard
git push -u origin main

Terminal output after pushing crypto dashboard code to GitHub

GitHub repository page showing uploaded crypto dashboard files

Deploying to Cloudways

I will open the Cloudways console and click Velocity. Then I will click Get Started. I’ll select the Starter plan for this deployment.

Cloudways Velocity Starter plan selection screen

Cloudways Velocity deployment setup screen

On the next screen, I click Connect Via Git. I link my GitHub account and select my crypto-dashboard repository.

Cloudways Connect Via Git screen linking GitHub account

Cloudways screen for selecting crypto dashboard GitHub repository

Now comes the most important deployment step. I connect my GitHub repo. Cloudways auto-detects the project as Vite CSR.

Cloudways auto-detecting project framework as Vite CSR

It does not select React.

Why?

Because Cloudways scans my package.json file. It sees Vite is the actual build engine. React is just the UI library.

But I do not keep the Vite CSR default either. Vite CSR assumes my app is a purely static site. It hides the server settings.

I built a custom Express backend to handle our SPA routing. So, I open the frameworks drop-down. I manually select the Express preset instead. This tells Cloudways to run a live Node process.

Cloudways framework dropdown with Express preset selected

Now the right settings appear. I open the “Build and output settings” option. I switch the package manager from Yarn to npm. I point the Entry File directly to server.js.

Cloudways build and output settings with server.js entry file

I add one final setting. I create an Environment Variable with the key NODE_ENV and the value production. This ensures Express runs efficiently.

Cloudways environment variable settings for NODE_ENV production

These are all the settings on the Cloudways side, I just need to make one change to my project. I’ll add “postinstall”: “npm run build” to my scripts in the package.json file. This’ll force Cloudways to build my React files automatically.

VS Code package.json with postinstall build script added

With that, my local code is ready. I commit the changes and push to GitHub:

git add package.json
git commit -m "chore: add postinstall build script"
git push origin main

Finally, I hit Deploy Now. Cloudways takes over completely. It pulls my code from GitHub. It installs the packages. It runs the build script. It boots up my custom Express engine.

Cloudways deployment progress after clicking Deploy Now

Cloudways deployment log showing build and install steps

Cloudways deployment completed confirmation screen

The deployment takes a minute or two. Once it finishes, I click the live Cloudways URL to see the result.

Cloudways live application URL after successful deployment

The page loads in a blink. The crypto logos and live prices render perfectly. My custom Express server handles the React dashboard flawlessly on the very first try.

Live crypto dashboard React app running after Cloudways deployment

Wrapping Up

Getting a React app out of development doesn’t have to be frustrating.

React needs to be built into static files. An Express server is the ultimate way to handle client-side routing. Once you realize that, the entire workflow clicks into place.

Utilize the Express preset on Cloudways managed Node.js hosting Velocity paired with a custom server.js file. You bypass the brutal 404 errors of standard static hosts. You avoid the latency penalties of serverless edge functions.

The end result is a lightning-fast application. It hooks your users from the very first second.

The entire project is on my GitHub. Feel free to use it.

Q. Why do React Router links break on refresh in production?

Vite or Webpack intercepts your URL requests locally. They route them back to React. In production on a static host, the server looks for a physical file matching your URL path. You are on /settings and hit refresh. The server looks for settings.html. It fails. It gives you a 404. A custom Express server fixes this. It uses a catch-all route to always serve index.html.

Q. How do I update my app after the first deployment?

Make your changes locally. Commit them. Push the code to your GitHub repository. Go to the Deployment Management tab in the Cloudways dashboard. If you have the “Auto-deployment” toggle turned on, you do not have to do anything else. Cloudways detects the push automatically. If you prefer manual control, just click the Redeploy button.

Q. Is Vite better than Create React App for production?

Yes. Create React App is officially deprecated by the React team. It uses Webpack under the hood. That becomes painfully slow as your project grows. Vite uses esbuild and Rollup. This results in significantly faster local server start times. Production builds are much quicker too.

Share your opinion in the comment section. COMMENT NOW

Share This Article

Abdul Rehman

Abdul is a tech-savvy, coffee-fueled, and creatively driven marketer who loves keeping up with the latest software updates and tech gadgets. He's also a skilled technical writer who can explain complex concepts simply for a broad audience. Abdul enjoys sharing his knowledge of the Cloud industry through user manuals, documentation, and blog posts.

×

Webinar: How to Get 100% Scores on Core Web Vitals

Join Joe Williams & Aleksandar Savkovic on 29th of March, 2021.

Do you like what you read?

Get the Latest Updates

Share Your Feedback

Please insert Content

Thank you for your feedback!

Do you like what you read?

Get the Latest Updates

Share Your Feedback

Please insert Content

Thank you for your feedback!

Want to Experience the Cloudways Platform in Its Full Glory?

Take a FREE guided tour of Cloudways and see for yourself how easily you can manage your server & apps on the leading cloud-hosting platform.

Start my tour