Published on · Updated on: · By Marco Ricci

- 11 min read

Read This Before Deploying Your React App on Railway

img of Read This Before Deploying Your React App on Railway

✨ Summarize this content with AI

You can deploy a React app on Railway in under 10 minutes. But Railway does not serve static files the way most developers expect. Without a web server config, your app builds fine, deploys with a green checkmark, and then either shows a blank page or breaks every time someone refreshes on a route. This guide covers the exact steps, the two configs that fix those errors, and when Kuberns is the faster path.

What You Need Before You Deploy

Prerequisites for deploying a React app on Railway

Before opening the Railway dashboard, make sure you have the following in place:

  • A React app using Vite (recommended) or Create React App that runs locally without errors
  • Your code pushed to a GitHub repository
  • A Railway account (free tier is available)
  • Node.js and npm installed locally

One important thing to sort out before you set any environment variables: Vite and Create React App use different prefixes. Vite requires VITE_ on every variable you want accessible in the browser. Create React App requires REACT_APP_. Using the wrong prefix means the variable silently resolves to undefined in production with no warning during the build.

Deploying a backend alongside your React app? Read how to deploy Node.js on Railway before you connect the two services.

How to Deploy a React App on Railway

How to deploy a React app on Railway step by step

Step 1: Create a New Project on Railway

Log into railway.com, click New Project, and select Deploy from GitHub repo. Authorise Railway to access your repositories and choose your React project.

Railway detects the Node.js runtime via your package.json and starts a build automatically using Nixpacks. It runs npm run build and produces the output in the dist/ folder for Vite or the build/ folder for CRA.

Step 2: Add a Web Server to Serve the Static Output

This is where nearly every guide stops and where nearly every first deploy breaks. Railway builds your React app correctly but has no web server pointing at the output folder. The container starts, finds nothing to run, and either crashes or returns a blank page.

The fix is to add a Caddyfile in your project root. Caddy is a lightweight web server and the approach Railway recommends in its own SPA guide.

Create a file called Caddyfile at the root of your project with the following content:

   {
    admin off
    persist_config off
    auto_https off
}

:{$PORT:3000} {
    root * /app/dist
    file_server
    try_files {path} /index.html
}

For Create React App, change /app/dist to /app/build.

Then install Caddy in your build process by adding a nixpacks.toml to your project root:

   [phases.setup]
nixPkgs = ["caddy"]

[start]
cmd = "caddy run --config /app/Caddyfile"

Push both files to GitHub. Railway will pick them up on the next deploy.

Step 3: Set Your Environment Variables

Open the Variables tab in your Railway service. Add every variable your React app reads at build time.

For Vite apps, prefix every frontend variable with VITE_:

   VITE_API_URL=https://api.yourdomain.com
VITE_ANALYTICS_KEY=your-key-here

Access them in your code using import.meta.env.VITE_API_URL.

For CRA apps, use REACT_APP_ and access via process.env.REACT_APP_API_URL.

Never put secrets like private API keys or database credentials in frontend environment variables. They get bundled into your JavaScript output and are visible to anyone who inspects your app in the browser. Frontend variables are for public endpoints and configuration only.

Step 4: Generate a Public Domain

Once your deploy completes, go to Settings and then Networking. Click Generate Domain to get a public *.up.railway.app URL. You can also connect a custom domain here and Railway will provision an SSL certificate automatically.

If you want to understand what Railway gives you on the free tier before you go live, read Railway free tier explained.

Deploy your React app on Kuberns

The Two Problems Every React Developer Hits on Railway

Two common React deployment problems on Railway

Both of these problems happen after a successful deploy. The Railway dashboard shows green. The build log shows no errors. But the app is broken.

Blank Page After Deploy

What you see: The Railway deploy succeeds but your app URL shows a completely blank page or the browser tab title never updates from the default.

Why it happens: Railway ran npm run build and created your dist/ folder. But there is nothing configured to actually serve those files to a browser request. The container starts, Railway sends a request to it, and gets no response.

The fix: Add the Caddyfile and nixpacks.toml described in Step 2 above. The file_server directive in Caddy points at your dist/ folder and serves files correctly. Push, redeploy, and the blank page is gone.

404 Error on Page Refresh or Direct URL

What you see: Your app loads fine from the root URL. You navigate to /dashboard. Everything works. You hit refresh. Railway returns a 404.

Why it happens: React Router uses the browser’s History API to change the URL without making a server request. When you navigate inside the app, React handles it entirely in the browser. But when you refresh, the browser sends a fresh HTTP request to the server for /dashboard. The server has no file at that path. It returns 404.

The fix: The try_files {path} /index.html line in the Caddyfile solves this. It tells Caddy: serve the file if it exists, and if it does not, fall back to index.html. React Router then reads the URL and renders the correct page on the client side. No server-side route definitions needed.

Both problems are solved by the same two files. The Caddyfile handles web server setup and the nixpacks.toml makes sure Caddy is installed during the build.

These errors are part of a broader pattern covered in why your app works locally but breaks after deploy.

Environment Variables in a React App on Railway

Managing environment variables for React on Railway

Environment variables in React apps work differently from backend apps because they are baked into the JavaScript bundle at build time, not read at runtime.

For Vite apps:

Variables must be prefixed with VITE_ to be included in the build output. Any variable without this prefix is stripped out before the bundle is created.

   // Correct
const apiUrl = import.meta.env.VITE_API_URL;

// Wrong - this will be undefined in production
const apiUrl = import.meta.env.API_URL;

For Create React App:

Variables must be prefixed with REACT_APP_.

   const apiUrl = process.env.REACT_APP_API_URL;

Key things to get right on Railway:

Set all variables in the Railway Variables tab before triggering a deploy. Since they are baked in at build time, adding a variable after the build does not take effect until you redeploy. Railway does not automatically rebuild when you add a new variable unless you trigger a redeploy manually.

Never put anything sensitive in frontend environment variables. API keys with write access, database connection strings, JWT secrets should never exist in your React app code. They end up in the browser bundle and are readable by any user.

Railway Limitations Worth Knowing Before You Go Live

Railway limitations for React deployments

Railway works for React, but there are constraints worth understanding before you commit to it for a production app.

Usage-based billing has no cap by default: Railway bills per CPU and memory second. A traffic spike or a build loop can push your monthly bill unexpectedly high. Set a spending limit manually in the Railway dashboard before your app gets real traffic.

React is a frontend-only deploy: If your app calls a backend API, that backend needs to be deployed as a separate Railway service. This means two services, two sets of environment variables, and separate domains to manage. Managing CORS between them adds another configuration step.

Free tier credits are one-time: The $5 starter credit does not renew. Once it runs out, billing starts on the Hobby plan with no grace period. Factor this in if you are testing before committing.

Env var changes require a manual redeploy: Unlike platforms that inject variables at runtime, Railway bakes them into the build. Adding or changing a variable after deploy means triggering a full rebuild to see the effect.

See how Railway pricing and limits compare against other platforms in the Railway vs Render vs Kuberns breakdown.

Deploy Your React App With AI in 3 Steps

Deploy React on Kuberns with AI in 3 steps

Every problem in the Railway setup above exists because the platform does not know your stack until you explain it through config files. The Caddyfile, the nixpacks.toml, the VITE_ prefix: these are all things you figure out manually, usually after a broken deploy.

Kuberns is an agentic AI cloud deployment platform. You connect your repo and the AI handles the rest.

Step 1: Connect your GitHub repo. Kuberns reads your repository, detects React and Vite automatically, and configures the entire build pipeline. No Caddyfile. No nixpacks.toml. No web server config to write.

Step 2: Set your environment variables. Add your variables in the Kuberns dashboard. The AI scans your codebase, identifies which environment variables your app is reading, and flags any that are missing before the deploy runs. No silent undefined values in production.

Step 3: Click deploy. Kuberns builds your React app, serves the static output with the correct configuration, sets up the try_files fallback so React Router works on every refresh, provisions SSL for your custom domain, and gets your app live. If something goes wrong, the agentic AI diagnoses the failure, explains what caused it in plain language, and suggests the exact fix.

No blank page. No 404 on refresh. No config files to debug at midnight.

React is just the frontend. If you are also deploying a full stack app with a backend, read how to deploy a full stack app with AI to see how Kuberns handles both sides from a single repo.

Deploy on Kuberns

Conclusion

Railway can host a React app, but it requires more setup than most deployment guides mention. You need a Caddyfile to serve the static output, a nixpacks.toml to install Caddy during the build, and the try_files fallback to fix the 404 on refresh that breaks React Router. Environment variables need the VITE_ prefix and a manual redeploy any time they change.

Once that setup is done, Railway works. But if you are deploying a React app and want to skip the configuration entirely, Kuberns handles all of it on first deploy automatically.

Deploy your React app on Kuberns with no config required

Frequently Asked Questions

Can you deploy a React app on Railway?

Yes. Railway can deploy a React app from a GitHub repository. It auto-detects Node.js, runs npm run build, and produces the static output. However, Railway does not serve static files by default. You need to add a web server config such as a Caddyfile to actually serve your built app.

Why is my React app showing a blank page on Railway?

A blank page after a successful Railway deploy means the build ran correctly but nothing is serving the output. Railway built your dist/ folder but has no web server pointing at it. Fix this by adding a Caddyfile to your project root that serves the dist/ folder and falls back to index.html for all routes.

Why does my React app return 404 on page refresh on Railway?

This is a React Router issue specific to how static files are served. When a user refreshes on a route like /dashboard, the server looks for a physical file at that path. Since there is none, it returns 404. The fix is to configure your web server to serve index.html for all unmatched routes and let React Router handle navigation client-side.

How do I set environment variables for a React Vite app on Railway?

In Railway, add your variables in the Variables tab of your service. For Vite apps, every variable that needs to be accessible in the browser must be prefixed with VITE_. For example: VITE_API_URL. Access it in your code with import.meta.env.VITE_API_URL. Variables without this prefix are not exposed to the browser bundle.

Does Railway support React Router?

Yes, but you need to configure fallback routing manually. React Router uses client-side navigation, which works fine after the initial load. The problem is direct URL access and page refreshes. Configure your Caddyfile to return index.html for all routes so React Router can take over.

What is the difference between deploying React on Railway vs Kuberns?

Railway requires a manual web server setup to serve a React app and a Caddyfile to fix the 404 on refresh issue. Kuberns detects React automatically, serves the built output without any config, and handles custom domains and SSL automatically. There are no config files to write and no post-deploy errors to debug.