How to Deploy a React or Next.js App

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.

Build output by app type
App typeBuild commandUpload or serveNeeds SPA rewrite?
Vite + Reactnpm run builddist/Yes
Next.js static exportnpm run build (output: 'export')out/No, one HTML file per route
Next.js with server featuresnpm run build, then npm run start.next/ on a Node.js serverNo

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.

Deploy recipe picker

Every app and host pair

Vite + React: Vercel

Import the repository; Vercel detects Vite. Add this file at the project root so deep links load the app.

{
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}

Vite + React: Netlify

Build command npm run build, publish directory dist. Save this line as public/_redirects so Vite copies it into dist.

/*    /index.html   200

Vite + React: Cloudflare Pages

Connect 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:  dist

Vite + React: GitHub Pages

Set 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/",
});

Next.js static export: Vercel

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" };

Next.js static export: Netlify

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"

Next.js static export: Cloudflare Pages

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:  out

Next.js static export: GitHub Pages

Static 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",
};

Full Next.js app: Vercel

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 # production

Full Next.js app: Netlify

Netlify 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"

Full Next.js app: Cloudflare Pages

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/nextjs

Full Next.js app: GitHub Pages

Not 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

SPA rewrite rules by host

Where the catch-all rule goes (from each host's docs, October 2026)
HostFileRule
Vercelvercel.jsonrewrites: source /(.*) to destination /index.html
Netlify_redirects or netlify.toml/* /index.html 200
Cloudflare PagesNoneAutomatic when there is no top-level 404.html
GitHub Pages404.html onlyNo rewrite rules; copy index.html to 404.html as a workaround

Netlify rewrite in netlify.toml

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 = 200

GitHub Actions workflow for a Vite site

Adapted 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

Step one: a clean production build

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.

Step two: routing that survives a refresh

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.

Step three: a domain and a check

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.

How it works

  1. Pick the kind of app you built: a Vite single page app, a Next.js static export or a full Next.js app.
  2. Pick the host you want to use.
  3. Copy the build settings and the config file shown, commit it, and connect the repository or upload the folder.
  4. After the first deploy, open a deep link such as /about directly and refresh it to confirm routing works.

Tips

Frequently asked questions

Why does my React app show 404 when I refresh a page?

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.

Do I need a rewrite on Cloudflare Pages?

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.

Can I deploy a Next.js app to GitHub Pages?

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.

What is the difference between a redirect and a rewrite?

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.

Where do environment variables go?

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.

More in Guides

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.

Product

Who It's For

Features

Resources

Company

View Sitemap