To deploy a React app, run the production build, then give the output folder to a host: dist for Vite, out for a Next.js static export. Single page apps also need a rewrite that sends unknown paths to index.html, or refreshing a deep link returns 404. A full Next.js app needs a host that runs Node.js.
| App type | Build command | Upload or serve | Needs SPA rewrite? |
|---|---|---|---|
| Vite + React | npm run build | dist/ | Yes |
| Next.js static export | npm run build (output: 'export') | out/ | No, one HTML file per route |
| Next.js with server features | npm run build, then npm run start | .next/ on a Node.js server | No |
Updated
Most failed React deploys come down to three things: the wrong output folder, a missing rewrite for client-side routes, or a Next.js feature that needs a server sent to a static host. Choose your app type and host below to see the exact setting and file for that pair, each taken from the host's own documentation.
Import the repository; Vercel detects Vite. Add this file at the project root so deep links load the app.
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}Build command npm run build, publish directory dist. Save this line as public/_redirects so Vite copies it into dist.
/* /index.html 200Connect the repository and choose the Vite preset. No rule is needed while dist has no top-level 404.html, because Pages then serves the root index for unknown paths.
Build command: npm run build
Build output dir: distSet base to your repository name, build with GitHub Actions and publish dist. GitHub Pages has a custom 404.html but no rewrite rules, so a common workaround is to copy index.html to 404.html after the build; those deep links then load with a 404 status.
export default defineConfig({
plugins: [react()],
base: "/my-repo/",
});Vercel builds Next.js directly, so a static export is optional here. Keep it only if you want the same files everywhere.
module.exports = { output: "export" };Set the publish directory to out. Each route already has its own HTML file, so no catch-all rewrite is needed.
[build]
command = "npm run build"
publish = "out"Use npm run build with output directory out. The export includes a 404.html, so Pages serves it for missing paths instead of the home page.
Build command: npm run build
Build output dir: outStatic export is the only way to run Next.js here. If the site lives under a repository path, set basePath to match it.
module.exports = {
output: "export",
basePath: "/my-repo",
};Import the repository and deploy. Vercel is one of the verified adapters listed in the Next.js deployment docs.
npm i -g vercel
vercel # preview deployment
vercel --prod # productionNetlify runs Next.js through its own integration, which the Next.js docs list as not yet a verified adapter. Check its docs for any feature gaps.
[build]
command = "npm run build"Cloudflare runs Next.js on Workers through its own integration, also not yet a verified adapter. Follow its framework guide rather than the Pages static flow.
https://developers.cloudflare.com/workers/frameworks/framework-guides/nextjsNot possible. GitHub Pages only serves files and cannot run next start. Switch to a static export or pick a host that runs Node.js.
# On any Node.js host instead:
npm ci && npm run build && npm run start| Host | File | Rule |
|---|---|---|
| Vercel | vercel.json | rewrites: source /(.*) to destination /index.html |
| Netlify | _redirects or netlify.toml | /* /index.html 200 |
| Cloudflare Pages | None | Automatic when there is no top-level 404.html |
| GitHub Pages | 404.html only | No rewrite rules; copy index.html to 404.html as a workaround |
The same rule as the one-line _redirects file. The 200 status keeps the address the visitor asked for.
[[redirects]]
from = "/*"
to = "/index.html"
status = 200Adapted from the workflow in Vite's deploy guide (which pins each action to a commit). It builds on every push to main and publishes dist. Turn on GitHub Actions as the source under Settings, then Pages.
name: Deploy
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
- uses: actions/configure-pages@v6
- run: npm ci
- run: npm run build && cp dist/index.html dist/404.html
- uses: actions/upload-pages-artifact@v5
with:
path: dist
- uses: actions/deploy-pages@v5
Install dependencies from the lockfile with npm ci, then run npm run build. For Vite, check the result with npm run preview, which serves dist on port 4173. For Next.js, npm run start serves the build on port 3000.
If the build passes locally, the host only needs three settings: the install command, the build command and the output folder. Most hosts detect Vite and Next.js and fill them in.
Client-side routers change the URL without asking the server. That works until someone refreshes or opens a shared link, and the server looks for a file that is not there.
The fix is a catch-all rule that returns index.html with a 200 status for paths that do not match a real file. Static exports from Next.js avoid this because every route gets its own HTML file.
Add your custom domain in the host's dashboard and create the DNS record it asks for. Certificates for HTTPS are issued by the host once the record resolves.
Finish by loading the home page, a deep link and a missing page. You want the app on the first two and a real not found screen on the third.
The router lives in the browser, but a refresh asks the server for a file such as /about/index.html that does not exist. A rewrite that returns index.html for unknown paths lets the router take over again. Each host has its own syntax for that rule.
Not if your build has no top-level 404.html. Cloudflare's docs say Pages then treats the project as a single page app and serves the root index for any path that is not a real file.
Only as a static export with output: 'export'. GitHub Pages serves files and cannot run the Next.js server, so features such as server actions, cookies, rewrites and on-demand rendering are unavailable there.
A redirect sends the browser to a new URL, which changes the address bar. A rewrite serves a different file while keeping the requested URL, which is what a single page app needs. Netlify marks a rewrite with status 200.
Set them in the host's project settings, not in a committed .env file. Remember that variables shipped to the browser are public, so keep keys that must stay private on a server or in serverless functions.
Related: Vite vs Next.js, React Router examples, Domain vs hosting, DNS propagation checker, npm commands cheat sheet.
Start for free or see all free tools. We.Inc is free to start, with no card needed. AI building is on paid plans from $20 a month.
We.Inc is an AI-powered website builder you can resell under your own brand. Launch a branded client dashboard, bill on Stripe Connect, and deliver AI-generated websites in minutes. White-label plans start at $99 a month for 25 client sites, with a 7-day free trial and no per-site fees.