Skip to content

Tutorial · Verified May 2026

How to Deploy a React App to Netlify: Complete 2026 Guide

TL;DR

  • Easiest path: push your React project to GitHub, log into Netlify, click "Add new site → Import existing project," pick the repo. Netlify auto-detects Vite/CRA/Next.js and ships the first deploy in 60–90 seconds.
  • Drag-and-drop path: for a one-off prototype, drop your dist/ or build/ folder onto netlify.com/drop — live URL in 30 seconds, no account required.
  • Environment variables: set in Netlify dashboard under Site → Environment variables; expose to React with the VITE_ or REACT_APP_ prefix.
  • SPA routing: add a _redirects file with /* /index.html 200 so deep links don't 404.
  • Custom domains and SSL: free on every plan, auto-renewed, no manual cert work.
Table of contents

Shipping a React app to Netlify takes about 90 seconds once you know the steps. This guide walks through the full path — Vite, Create React App, Next.js, and React Router — with environment variables, custom domains, and the gotchas that catch most first-timers.

By ToolChase Editorial· 2026-05-08· 14 min read

This post contains affiliate links. We may earn a commission at no extra cost to you. Pricing verified May 2026 from official sources.

React's the most popular frontend framework in 2026 by deployment volume — over 60% of new public-facing apps surveyed by State of JS use either React directly or a React-based meta-framework (Next.js, Remix, React Router v7). Netlify is one of the two best places to ship those apps (the other being Vercel; we wrote a head-to-head comparison here). This guide covers every standard React deploy scenario plus the edge cases that cause first-time deploys to fail.

Before we start: the path you take depends on which React tooling your project uses. Pick the matching section: Vite (now the default for new React projects since 2024), Create React App (CRA, technically deprecated but still common), Next.js (App Router or Pages Router), or React Router v7 (the framework formerly known as Remix). The deploy mechanics are 90% the same; the framework-specific bits matter for build output paths and SSR vs static.

Path A: Deploy a Vite + React app (most common in 2026)

Vite is the default tool for new React projects in 2026. The deploy is straightforward — Netlify auto-detects Vite, runs the build, and serves the static output.

Step 1 — Make sure your build works locally

From your project root:

npm install
npm run build
npm run preview

The build command should produce a dist/ folder with index.html and bundled JS/CSS in dist/assets/. The preview command serves that build locally so you can verify it works before deploying. If preview shows a working app at http://localhost:4173, you're ready to ship.

Step 2 — Push to GitHub (or GitLab / Bitbucket)

Netlify reads from any of the three major Git hosts. If your project isn't already in a Git repo:

git init
git add .
git commit -m "initial commit"
gh repo create my-react-app --public --source=. --push

Replace my-react-app with your repo name. The gh CLI assumes you've installed and authenticated GitHub CLI; if not, create the repo manually at github.com and push with git push origin main.

Step 3 — Connect to Netlify

Sign up at netlify.com (free, no credit card). From the dashboard, click Add new site → Import existing project. Choose your Git provider, authorize the Netlify GitHub app on the repo (or org), then pick the repo from the list. Netlify reads your package.json and detects Vite automatically.

Step 4 — Configure the build settings

For a Vite + React project, Netlify auto-fills:

  • Base directory: empty (project root)
  • Build command: npm run build
  • Publish directory: dist
  • Functions directory: netlify/functions (only if you're using Netlify Functions)

You can override these if your monorepo has a non-standard layout. For a fresh Vite project, the defaults are correct — click Deploy site.

Step 5 — Wait 60–90 seconds

Netlify clones the repo, runs npm install (using a cached node_modules from prior deploys when possible), runs npm run build, and uploads dist/ to the global CDN. The build log streams in real time. When done, you get a randomly-generated subdomain like amazing-bird-12345.netlify.app.

Don't have a Netlify account yet?

Free tier is genuinely free — 300 credits per month, custom domains with SSL, and the Functions / Database stack included. No credit card to start.

Try Netlify Free →

Path B: Deploy a Create React App (CRA) project

CRA is officially deprecated, but plenty of projects still use it. The deploy works identically to Vite with two settings changes:

  • Build command: npm run build (same)
  • Publish directory: build (CRA outputs to build/, not dist/)

Netlify auto-detects CRA and sets these correctly. If you've migrated CRA away from a fresh template (modified package.json scripts, etc.), you may need to override the publish directory in the Build settings panel.

Migration note: If you're starting a new project in 2026, do not use Create React App. The React team has officially deprecated it. Migrate to Vite (npm create vite@latest -- --template react) or to a framework like Next.js or React Router. CRA migration is usually 1–3 hours for a small app.

Path C: Deploy a Next.js app

Next.js needs the @netlify/plugin-nextjs build plugin to handle SSR, ISR, and the App Router correctly. The plugin is auto-installed when Netlify detects Next.js in your package.json, so most deploys "just work" without manual configuration.

Verify the plugin is active

After your first deploy, check the build log for the line: Now using @netlify/plugin-nextjs version X.X.X. If you don't see it, add the plugin manually to netlify.toml:

[build]
  command = "npm run build"

[[plugins]]
  package = "@netlify/plugin-nextjs"

Pages Router vs App Router

The plugin supports both routers. App Router (app/ directory) deploys with full RSC streaming, partial pre-rendering, and ISR working out of the box. Pages Router (pages/ directory) deploys with API routes mapped to Netlify Functions. ISR revalidation works on both — re-deploys do not invalidate the entire cache.

For Next.js 15+ features (the bleeding edge), Netlify's plugin typically lags Vercel by 1–4 weeks. If you must ship a feature on day one, deploy to Vercel; otherwise Netlify catches up quickly and works at parity.

Path D: Deploy a React Router v7 (formerly Remix) app

React Router v7 is the renamed continuation of Remix. The deploy uses the official Netlify adapter:

npm install @netlify/remix-adapter

Then in your vite.config.ts, configure the adapter:

import { reactRouter } from '@react-router/dev/vite';
import { netlifyPlugin } from '@netlify/remix-adapter/plugin';
import { defineConfig } from 'vite';

export default defineConfig({
  plugins: [reactRouter(), netlifyPlugin()]
});

This produces server-side rendered pages running on Netlify Functions and static assets on the CDN. The deploy mechanics from there are identical to Vite — push, connect, deploy.

The Netlify Drop alternative — for one-off deploys

Sometimes you don't want a Git repo, an account, or a build pipeline. You just want to share a working URL of a static site you built locally. Netlify Drop handles this in 30 seconds.

  1. Build your project locally (npm run build) so you have a static dist/ or build/ folder
  2. Open netlify.com/drop in a browser
  3. Drag the dist/ folder (the entire folder, not its contents) onto the upload zone
  4. Wait 10–30 seconds; Netlify provisions a public HTTPS URL
  5. Copy the URL and share it

Drop sites have no account, no Git connection, and a 24-hour edit window before they auto-expire. To make a Drop site permanent, click "Claim site" and link it to a free Netlify account. Drop is ideal for prototypes, design previews, hackathon submissions, or quick file shares.

Have a static prototype to share right now?

Try Netlify Drop — drag a folder, get a live URL in 30 seconds. No account or Git repo required. Claim it later if you decide to keep it.

Open Netlify Drop →

Common configuration: SPA routing, env vars, headers

SPA routing — preventing 404s on deep links

React apps using client-side routing (React Router, TanStack Router, etc.) need every URL to serve index.html so the JS router can handle the route. Without this, a direct visit to /about or a refresh on a deep link returns a 404.

Add a public/_redirects file (Vite) or a public/_redirects file (CRA) with this content:

/*    /index.html   200

This tells Netlify: "For any path, serve /index.html with a 200 status code." The build copies the file to your output, and Netlify reads it during deploy. After this, every URL works regardless of whether it matches a file.

For Next.js and React Router v7, you don't need this — those frameworks handle SPA routing differently and the Netlify plugin/adapter sets up the right rules automatically.

Environment variables — the right way

React apps need environment variables exposed to the browser bundle. Both Vite and CRA require a specific prefix to indicate the var is safe to bundle (without it, the var stays server-only and is undefined in the browser).

  • Vite: use the VITE_ prefix (e.g. VITE_API_URL=https://api.example.com) and reference in code as import.meta.env.VITE_API_URL
  • CRA: use the REACT_APP_ prefix and reference as process.env.REACT_APP_API_URL
  • Next.js (server-side): any name works (e.g. DATABASE_URL); access via process.env.DATABASE_URL
  • Next.js (browser-bundled): use the NEXT_PUBLIC_ prefix

Set the values in the Netlify dashboard under Site → Environment variables. They're encrypted at rest, decrypted only during build, and never exposed in the public asset bundle (unless you've prefixed them, in which case they're embedded by design — don't put secrets in VITE_, REACT_APP_, or NEXT_PUBLIC_ variables).

Security warning: Anything prefixed VITE_, REACT_APP_, or NEXT_PUBLIC_ ends up in the JavaScript bundle that ships to every visitor. Treat those values as public. Real secrets (Stripe secret keys, database passwords, third-party API keys with write access) belong in server-only env vars and only get accessed from Netlify Functions or Next.js server components.

Custom domain and SSL

After your first deploy, the site is at a randomly-generated *.netlify.app URL. To use a custom domain like www.mysite.com:

  1. Go to Site → Domain management in the Netlify dashboard
  2. Click Add custom domain, enter your domain
  3. Choose: register through Netlify (one-click DNS setup), or point existing DNS at Netlify (A and AAAA records, or CNAME for subdomains)
  4. Wait for DNS propagation (typically 5 minutes to 1 hour)
  5. Netlify auto-issues a Let's Encrypt SSL certificate within 15 minutes of DNS pointing correctly

SSL is free, auto-renewed, and supports HTTP/3. There's no manual certificate work, no Cloudflare layer required, and no separate cost — included on every plan including Free.

Build optimization — keeping deploys fast and small

A few habits keep your Netlify deploys fast and your monthly credit bill low:

  • Cache node_modules: Netlify does this automatically — your second deploy is dramatically faster than the first because dependencies don't reinstall.
  • Use a .netlify/cache directory for any build-time computed data your app generates that you can reuse across deploys.
  • Tree-shake aggressively: import only what you need from large libraries. import { Button } from 'mui-package' is much smaller than import * as Mui from 'mui-package'.
  • Use the Netlify image optimizer (build plugin, free): cuts image bandwidth 40–60% with WebP/AVIF conversion at build time.
  • Add long Cache-Control headers to fingerprinted assets via _headers file: /assets/* Cache-Control: public, max-age=31536000, immutable
  • Run npm prune in the build command to drop devDependencies from the final image when relevant.

Common errors and how to fix them

Build fails with "Module not found"

Usually a case-sensitivity mismatch. macOS is case-insensitive but Linux (where Netlify builds run) is not. import Button from './button' will work on macOS even if the file is Button.tsx — but it fails on Netlify. Fix the import case to match the actual file.

Site loads but routes 404 on deep link refresh

Missing _redirects file. See the SPA routing section above. Add /* /index.html 200 to public/_redirects and redeploy.

Environment variable returns undefined

Most common cause: missing prefix. API_URL won't be exposed to the browser; VITE_API_URL (Vite) or REACT_APP_API_URL (CRA) will. Re-deploy after changing var names — the build needs to re-bundle.

Custom domain shows "Site not found"

DNS hasn't propagated yet, or the records are pointing at the wrong target. Use dig www.mysite.com to verify the records resolve to Netlify's load balancer. If they're correct, wait 30–60 minutes for full propagation. If they're wrong, fix the records at your registrar.

Build hits the 1-hour timeout

Your build is too long for the Free/Personal plan's 1-hour build timeout. Either upgrade to Pro (no time limit), optimize the build (parallelize, drop unused deps), or split the project into smaller deploys (monorepo with multiple Netlify sites pointing at different subdirectories).

Going further

Once your basic deploy works, you can layer on Netlify's broader stack:

  • Netlify Functions for server-side logic — drop a netlify/functions/hello.js file and it auto-deploys as a serverless endpoint at /.netlify/functions/hello
  • Edge Functions for personalization, A/B testing, or geographic routing — runs at the CDN edge on Deno
  • Netlify Forms to capture form submissions without a backend — add data-netlify="true" to any form tag
  • Netlify Identity for user authentication — built-in JWT auth with social login and email/password
  • Netlify Database for managed Postgres — included on every plan
  • Deploy Previews on every PR — free on every plan, configurable for password protection on Pro

Most React projects start with just the static deploy and add Functions or Forms as needed. The full Netlify stack scales from "two-page personal site" to "production SaaS with auth, payments, and Postgres" without ever leaving the platform.

For the full feature breakdown and pricing math, see our Netlify review. For comparison against the most common alternative, see Netlify vs Vercel. For the credit-based pricing details, see our Netlify pricing guide.

Frequently asked questions

Is deploying a React app to Netlify free?

Yes. The Netlify Free plan ($0/month) includes 300 credits per month, custom domains with auto-renewing SSL, unlimited preview deploys, and Netlify Functions. A typical low-traffic React app uses well under 300 credits per month, so most personal projects stay on Free indefinitely. There's no credit card required to sign up. The 300-credit allowance covers roughly 15 GB of bandwidth or 30 GB-hours of serverless compute, which is more than enough for portfolios and small SaaS dashboards.

How long does it take to deploy a React app to Netlify?

From a fresh sign-up: 5–10 minutes total, dominated by initial GitHub authorization. The actual build-and-deploy step is 60–90 seconds for a typical Vite + React project. Subsequent deploys (after every Git push) are usually 30–60 seconds because Netlify caches your node_modules across builds. For a one-off Drop deploy with no account, the whole process is under 60 seconds.

Why does my React Router app 404 on direct URL or refresh?

You're missing a _redirects file. React apps using client-side routing (React Router, TanStack Router, etc.) need every URL to serve index.html so the JavaScript router can handle the route. Add a file at public/_redirects containing the line /* /index.html 200. Rebuild and redeploy. After this, deep links and refreshes work correctly.

How do I add environment variables to my React app on Netlify?

Go to your Netlify dashboard → Site → Environment variables → Add. For Vite, use the VITE_ prefix (e.g. VITE_API_URL). For CRA, use REACT_APP_. For Next.js browser code, use NEXT_PUBLIC_. After saving, trigger a new deploy — env vars are baked into the bundle at build time, so existing deploys won't pick them up automatically. For server-side env vars (used in Netlify Functions or Next.js server components), no prefix is needed.

Can I deploy a React app without a GitHub repo?

Yes — use Netlify Drop. Build your project locally (npm run build), then drag the dist/ or build/ folder onto netlify.com/drop. You'll get a live HTTPS URL in 30 seconds with no account, no Git connection, and no setup. Drop sites expire in 24 hours unless you claim them with a free Netlify account, so it's best for prototypes and one-off shares rather than production sites.

Does Netlify support Next.js?

Yes — fully. The @netlify/plugin-nextjs build plugin (auto-installed when Netlify detects Next.js) handles SSR, ISR, App Router, RSC streaming, and middleware. Most Next.js features ship to Netlify within 1–4 weeks of their release on Vercel. For mainline workloads, Netlify and Vercel are roughly equivalent. For bleeding-edge Next.js features on day one of release, Vercel is usually slightly ahead because the Vercel team maintains Next.js.

Do I need to configure anything in netlify.toml for a basic React deploy?

No. Netlify auto-detects Vite, CRA, Next.js, and most other React tooling from your package.json and configures the build settings correctly. netlify.toml is only needed for advanced configuration: custom build hooks, environment-specific deploys, redirect rules beyond a basic SPA fallback, custom plugins, or branch-specific settings. For a fresh Vite + React project, there's nothing to configure manually.

How do I use a custom domain with my React app?

In the Netlify dashboard, go to Site → Domain management → Add custom domain. Either register the domain through Netlify (one-click DNS) or point your existing DNS at Netlify (A record at apex, CNAME for www). DNS propagation takes 5 minutes to 1 hour. Netlify auto-issues a Let's Encrypt SSL certificate within 15 minutes of DNS pointing correctly. The whole process is under 30 minutes most of the time. SSL is free and auto-renewed forever — no manual cert work.

What's the difference between Netlify Drop and Netlify Sites?

Drop is for one-off deploys: drag a folder onto netlify.com/drop, get a temporary URL. No account, no Git, no rebuild on changes — every change requires a fresh drag. Sites are for ongoing projects: connect a Git repo, automatic rebuilds on every push, custom domains, environment variables, and the full Netlify feature set. Drop is for prototypes and demos; Sites is for anything you'll iterate on or ship to production.

My build works locally but fails on Netlify with "Module not found". What's wrong?

Almost always a case-sensitivity mismatch. macOS and Windows are case-insensitive filesystems by default; Netlify's build runs on Linux which is case-sensitive. import Button from './button' works on macOS even if the file is Button.tsx, but Linux fails the import. Fix every import case to match the actual filename exactly. The same gotcha applies to image and asset imports — check that ./logo.PNG matches the actual file capitalization.

Netlify ReviewFull feature breakdown →Netlify vs VercelWhich platform to choose →Netlify Pricing Guide 2026Free vs Pro vs Enterprise →Netlify Alternatives6 platforms compared →

More on ToolChase

Netlify full review → Netlify pricing explained 2026 → Netlify vs Vercel honest comparison → Netlify alternatives → Vercel — alternative deploy host → AI coding tools directory →