Vercel

Deploying React/Vite Apps

This lesson focuses on React apps built with Vite, but most of the ideas apply to any Vite frontend app. The goal is not only to get a URL. The goal is to understand what Vercel builds, what it serves, what remains private, and what you should verify before calling the deployment done.

What Vercel Deploys

A normal Vite React app is a frontend build. During deployment, Vercel installs dependencies, runs the build command, and serves the generated static files from the output directory. Vite's default production output directory is dist, and the usual build command is npm run build.

A minimal Vite project usually has scripts like this:

{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  }
}

The dev script is for local development. It runs a development server with fast reload behavior. The build script creates production files. The preview script serves the built output locally so you can check what production will roughly look like before deploying.

Do not upload your src directory and assume the browser runs it directly. The browser receives the compiled output. Vercel serves HTML, CSS, JavaScript, images, and other assets from the build output. The build step is where TypeScript, JSX, CSS processing, bundling, minification, and environment replacement happen.

The Basic Vercel Settings

When Vercel detects a Vite project, the default settings are usually close to what you need:

Framework preset: Vite
Install command: npm install, pnpm install, yarn install, or bun install
Build command: npm run build
Output directory: dist
Development command: npm run dev

You normally do not need a vercel.json file for a basic Vite deployment. Add one only when you need explicit routing, headers, redirects, or custom build behavior. Beginners often add configuration too early and then debug their own configuration instead of the app.

Before changing settings in the dashboard, check package.json. The package manager and lockfile should match. If the repo has package-lock.json, expect npm. If it has pnpm-lock.yaml, expect pnpm. If it has yarn.lock, expect Yarn. Vercel can infer many settings, but inconsistent lockfiles and copied scripts still cause avoidable build failures.

Deploy The First Version

The basic workflow is the same Git-based flow you used earlier in the track.

Create or open the Vite React project.
Commit the working app to Git.
Import the repository into Vercel.
Confirm the Vite preset, build command, and output directory.
Create the first Preview or Production Deployment.
Open the generated URL and test the app.

For a new learner project, keep the first deployment boring. Do not add custom domains, analytics, functions, storage, and complex rewrites in the same change. First prove that the production frontend build works. Then add the extra platform features one at a time.

A good first test is simple: load the homepage, click through each route or major view, refresh the browser on a nested route, open DevTools, and check the console and network panel for obvious errors. If the app uses remote APIs, check both a successful request and a failed request state.

Build Locally Before Deploying

Vite's local development server can hide production problems. Development mode may be more forgiving, may load different environment variables, and does not prove that the final bundled files work. Before pushing a deployment fix, run the production build locally.

npm run build
npm run preview

The first command creates dist. The second command serves the built output locally. This is not a perfect copy of Vercel's production edge network, but it catches many frontend problems: missing imports, build-time TypeScript errors, broken asset paths, routes that fail after build, and environment variables that were only present in development.

If npm run build fails locally, fix that before studying Vercel settings. If it only fails on Vercel, compare Node versions, package manager behavior, environment variables, and any file that exists locally but was ignored or never committed.

SPA Routing And Refresh 404s

Many React/Vite apps use client-side routing. A user clicks from / to /settings, and React Router or another router updates the visible URL without requesting a new HTML file from the server. That works during app navigation, but a direct browser refresh at /settings can fail if the platform looks for a real /settings file and does not find one.

The symptom is specific: clicking to a route works, but opening the same URL directly or refreshing it returns 404. That does not mean React is broken. It means the deployment needs a fallback rule for app routes.

A simple SPA fallback can rewrite app paths to index.html:

{
  "rewrites": [
    {
      "source": "/((?!api/|assets/).*)",
      "destination": "/index.html"
    }
  ]
}

Treat this as a starting point, not a rule to paste blindly. Your app may have different asset paths, API paths, documentation paths, or static files. The important idea is that client-side app routes should return the app shell, while real missing assets and API routes should still fail clearly.

After adding a fallback, test these cases:

/ loads the app.
/settings loads after direct refresh.
/assets/some-file.js does not return the homepage.
/api/missing does not return the homepage.
/a-path-that-should-not-exist behaves as expected.

A catch-all rewrite that turns every missing URL into a 200 response can make monitoring and debugging worse. Use the narrowest fallback that matches your app structure.

Environment Variables In Vite

Vite environment variables have an important browser boundary. Variables exposed to frontend code must use the configured public prefix, which is VITE_ by default. Vite replaces those values into the client bundle at build time. That is useful for public configuration, such as a public API base URL, a feature flag that is safe to reveal, or a public analytics site id.

It is not safe for secrets.

This is acceptable:

VITE_PUBLIC_API_BASE_URL=https://api.example.com
VITE_PUBLIC_SITE_NAME=Example App

This is unsafe:

VITE_STRIPE_SECRET_KEY=sk_live_do_not_do_this
VITE_DATABASE_PASSWORD=do_not_do_this
VITE_PRIVATE_EMAIL_TOKEN=do_not_do_this

If a value is prefixed with VITE_ and referenced by frontend code, assume a user can see it in the built JavaScript. Do not put private API keys, database credentials, email provider tokens, webhook secrets, session secrets, or payment secrets in Vite client variables.

For private work, use a server-side boundary. That might be a Vercel Function, a separate backend API, or a full-stack framework. The browser calls your server endpoint, and the server endpoint uses the secret. The frontend should receive only the result it is allowed to know.

Vercel Environment Scopes

Vercel lets you define Environment Variables for different deployment environments. For a Vite app, that means a Preview Deployment can use a staging API URL while Production uses the production API URL. This is useful, but it also means your build result depends on the environment in which it was built.

A common setup is:

Local: VITE_PUBLIC_API_BASE_URL=http://localhost:8000
Preview: VITE_PUBLIC_API_BASE_URL=https://staging-api.example.com
Production: VITE_PUBLIC_API_BASE_URL=https://api.example.com

Keep the names the same and change only the values by environment. Avoid inventing separate variable names like VITE_STAGING_API and VITE_PROD_API unless the app truly needs both at runtime. Usually the app should ask for one value, and the deployment environment should provide the right one.

After changing Vercel Environment Variables, redeploy. A Vite frontend bundle receives many values at build time, so changing the dashboard value does not necessarily rewrite JavaScript that was already built.

API Calls From The Browser

A Vite React app runs in the user's browser. Browser code can call public APIs, but it cannot safely protect private credentials. If the app talks directly to an external service, ask whether that service expects public browser clients or private server clients.

Good browser-side calls include public content APIs, your own public backend endpoints, and third-party services designed for browser use with domain restrictions and limited tokens. Risky browser-side calls include email sending APIs, payment secret-key operations, database admin APIs, private CRM APIs, and anything that allows privileged writes.

When in doubt, put a server endpoint in the middle. The React app sends a limited request to your endpoint. The endpoint validates it, uses the private secret, handles errors, and returns a safe response. Later lessons in this track cover Vercel Functions and private API calls in more detail.

Static Assets And Base Paths

Vite handles assets imported from source code and files placed in the public directory differently. Imported assets are processed by the bundler and often receive hashed filenames. Public files are copied as-is and are referenced from the site root.

For a Vercel app deployed at the root of a domain, the default Vite base path is usually fine. If you previously deployed the app under a subpath on another platform, check vite.config.js for a custom base setting. A stale base path can make CSS, JavaScript, or images load from the wrong URL after moving to Vercel.

Broken asset paths often appear as a blank page with 404s in the browser network panel. Do not debug that from the React component first. Check whether the built HTML is trying to load assets from the correct path.

Common Mistakes

The first mistake is deploying the development server instead of the production build. Vercel should build the app and serve the generated output, not run vite --host as the public production server for a static frontend.

The second mistake is setting the output directory to build because another framework used that name. Vite's default output is dist unless you changed it.

The third mistake is committing .env files with real secrets. Even if Vite does not expose an unprefixed variable to the browser bundle, secrets do not belong in the repository.

The fourth mistake is putting private keys in VITE_ variables. The prefix is a publishing decision.

The fifth mistake is adding an over-broad SPA rewrite that returns index.html for missing assets or API routes.

The sixth mistake is testing only the homepage. A React app can deploy with a working homepage and broken nested routes, broken API configuration, or missing static assets.

Deployment Checklist

Before merging a Vite deployment change, verify the build command, output directory, package manager, environment variables, and route behavior. Open the Preview Deployment, not only the local dev server. Test direct refreshes on nested routes. Inspect the browser console. Confirm that public configuration is public on purpose and that private secrets remain server-side.

For commercial, client, business, paid, ad-supported, or production business projects, also confirm that the Vercel plan and team ownership are appropriate. A Vite app may be technically easy to deploy, but commercial use still needs the billing and plan review covered earlier in this track.

Review Questions

  • What directory does Vite use for production output by default?
  • Why should you run npm run build before relying on a deployment fix?
  • Why can a React route work by clicking but fail after refresh?
  • What does the VITE_ prefix mean for secrets?
  • When should a frontend call a Vercel Function instead of a third-party API directly?
  • What should you test in a Preview Deployment before merging?

Official References