Deploy and share your app
Put your React app on the internet with a public link: build it, push it to GitHub, deploy on a free host such as Vercel or Netlify, fix the refresh-gives-404 problem, and handle environment variables safely.
What you'll do: take the recipe finder (or any Vite + React app) from localhost to a public URL you can put on your resume and send to a recruiter.
Prerequisites: a working Vite + React project, Node.js, Git installed, and a free GitHub account. You will also create a free account on one hosting service.
Free tiers: Vercel, Netlify, Cloudflare Pages and GitHub Pages all have free plans for personal projects at the time of writing (2026). Limits and terms change, so check the pricing page of the host you choose. Commercial use is often not covered by free plans.
What 'deploying' means
On your laptop, npm run dev starts a development server that rebuilds as you type. It is not meant for visitors. For production you run npm run build. Vite compiles, bundles and minifies your app into a folder of plain static files: one index.html, plus JavaScript, CSS and images. A static host simply serves that folder to anyone who asks.
So deploying = (1) build the folder, (2) give it to a host. Modern hosts connect to your GitHub repository and do both automatically every time you push code. This is a basic form of CI/CD (continuous integration and delivery).
Step 1: build locally first
Always check the build on your machine. Many deployment failures are build errors you would have seen locally.
npm run build
npm run previewbuild creates the dist folder. preview serves dist locally, usually at http://localhost:4173, so you see what visitors will see.
The build prints a list of files in dist/ (index.html, and files under assets/ with names like index-3f2a9c.js) with their sizes. Opening the preview address shows your app. If the build fails, the error names a file and line. Fix it before going on; hosts will fail in the same way.Linux is case-sensitive
Hosts run Linux. If your file is Menu.jsx but you import ./menu.jsx, it may work on Windows and fail on the host with 'module not found'. Make import paths match file names exactly, including capital letters.
Step 2: put the code on GitHub
Create a repository
- 1Check that
.gitignorein your project containsnode_modulesanddist(Vite's template already does). Never commitnode_modules. - 2Run
git init,git add .andgit commit -m "Recipe finder"in the project folder. - 3On github.com, click New repository, name it
recipe-finder, leave it empty (no README) and create it. - 4Copy the two commands GitHub shows under 'push an existing repository'. They look like the commands in the code block below, with your username.
- 5Refresh the repository page and confirm your files are there, and that there is no
.envfile with secrets.
git init
git add .
git commit -m "Recipe finder"
git branch -M main
git remote add origin https://github.com/YOUR-USERNAME/recipe-finder.git
git push -u origin mainReplace YOUR-USERNAME. If Git asks for a password, GitHub now needs a personal access token or the browser sign-in flow instead of your account password.
Step 3: fix single-page-app routing before you deploy
With React Router, /recipe/52772 is not a real file. In development, Vite knows to serve index.html for every URL. On a static host, if someone refreshes on /recipe/52772 or opens a shared link, the server looks for a file called recipe/52772, finds nothing, and returns 404. The fix is a rewrite rule: 'for any path with no matching file, serve index.html and let React Router handle it'.
Each host has its own way to say this. Add the one for your host now.
SPA fallback by host
| Host | What to add |
|---|---|
| Vercel | A file vercel.json in the project root (see below) |
| Netlify | A file public/_redirects containing /* /index.html 200 |
| Cloudflare Pages | A file public/_redirects with /* /index.html 200; check their docs, as a project without a 404.html page may also work as an SPA by default |
| GitHub Pages | No rewrites available: use HashRouter, or the 404.html trick (see the Pages note below) |
{
"rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}Vercel serves real files first (your JS and CSS in assets), and applies this rewrite only when no file matches.
/* /index.html 200Files in Vite's public folder are copied to dist unchanged. The 200 makes it a rewrite (the URL stays the same) instead of a redirect.
Step 4: deploy on Vercel
From GitHub to a live URL
- 1Commit and push the rewrite file:
git add .,git commit -m "Add SPA rewrite",git push. - 2Go to vercel.com and sign up with your GitHub account.
- 3Click Add New, then Project, and import your
recipe-finderrepository (grant access to it if asked). - 4Check the settings: Vercel recognises Vite. The build command should be
npm run buildand the output directorydist. You normally change nothing. - 5Click Deploy and wait about a minute. You get a URL such as
recipe-finder-abc123.vercel.app. - 6Open it, search for a recipe, open a recipe page and refresh. It should work, not show 404.
From now on, every git push to main triggers a new deployment automatically. Pushes to other branches get their own preview URLs, which are great for showing a change to a friend before it goes live.
Or: deploy on Netlify
The same idea, different buttons
- 1Add
public/_redirects(above), commit and push. - 2Sign up at netlify.com with GitHub, then choose Add new site, then Import an existing project.
- 3Pick your repository. Set Build command
npm run buildand Publish directorydist. - 4Deploy. Netlify gives you a
something.netlify.appURL, which you can rename in Site settings.
Quick share without GitHub
Netlify also has a drag-and-drop option: run npm run build and drop the dist folder onto their deploy page. It is the fastest way to share a demo. Use the GitHub route for anything you will keep changing.
GitHub Pages note
GitHub Pages is free and lives next to your code, but it hosts your app under a sub-path like username.github.io/recipe-finder/. Two changes are needed: set base: '/recipe-finder/' in vite.config.js so asset URLs are right, and use HashRouter instead of BrowserRouter in main.jsx, because Pages cannot rewrite paths (URLs then look like /#/recipe/52772). Then publish the dist folder with a GitHub Actions workflow or the gh-pages package; the Vite documentation has a current step-by-step guide under Deploying a Static Site.
Environment variables and secrets
Apps often need settings that differ between your laptop and production, such as the API address. In Vite, variables that start with VITE_ are read from a .env file and are available as import.meta.env.VITE_NAME. On a host, you add the same variable in the project's Settings, under Environment Variables, and redeploy, because the value is baked into the files at build time.
// .env file contents:
// VITE_API_URL=http://localhost:8000
// src/config.js
export const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:8000';
// anywhere else
// fetch(`${API_URL}/menu`)The first two lines are the content of a separate .env file, shown as comments here. In production, set VITE_API_URL to your deployed backend address.
Nothing in a frontend is secret
Everything in dist is downloaded by every visitor, including values from VITE_ variables. Never put a private API key, a database password or a payment secret in a React app, even in an .env file. Anyone can open DevTools and read it. Keys that must stay secret belong on a backend server (or a serverless function) that your app calls. Also add .env to .gitignore so you don't publish local settings by accident.
If your app talks to your own backend
The browser blocks requests from one origin to another unless the server allows it. This is CORS. If your React app at https://recipe-finder.vercel.app calls your FastAPI backend at another address, the backend must list your frontend's address as an allowed origin. Browsers also block a secure (https) page from calling an insecure (http) API, so deploy the backend with HTTPS too. The recipe finder uses TheMealDB, which already allows browser requests, so it needs none of this.
Make it presentable
Before you share the link
- The page has a proper
<title>and a favicon (editindex.html; the Vite template ships a default icon). - Add
<meta name="description" content="...">inindex.htmlso link previews have text. - It works on a phone: open the URL on your phone and test search, a recipe page and favourites.
- A refresh on a deep link (
/recipe/52772) works. - The README in your repository has a screenshot, the live URL, how to run it, and the features list.
- No console errors in DevTools, and no secrets in the repository.
- Lighthouse scores are reasonable (see *Performance and accessibility*).
Custom domain: all these hosts let you attach your own domain (for example yourname.dev) in their settings, after you buy one from a registrar and add the DNS records they show. It is optional; a .vercel.app or .netlify.app address is fine for a portfolio.
When it fails
Common deployment problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Build fails on the host but works locally | Wrong import capitalisation, a missing dependency in package.json, or a different Node version | Read the build log's first error; run npm install and npm run build after deleting node_modules locally |
| Blank white page | Wrong base path or a JavaScript error | Open DevTools Console and Network; check that files under assets load (no 404) |
| 404 on refresh | No SPA rewrite | Add vercel.json or _redirects as shown above |
| API calls fail only in production | Hard-coded localhost URL, missing env variable, CORS or http/https mismatch | Use VITE_API_URL, set it on the host and redeploy; check allowed origins on the backend |
| Old version still shows | Browser or CDN cache | Hard refresh (Ctrl+Shift+R) and check the latest deployment is marked live |
My React (Vite) app fails to deploy on [Vercel / Netlify / GitHub Pages]. Here are the last 40 lines of the build log:
[paste log]
My package.json scripts and vite.config.js are:
[paste]
Explain the first real error in simple words, what causes it, and the smallest change that fixes it. Tell me how to confirm the fix locally before pushing.What to expect: Paste the log from the first error, not only the last line. Check the suggested fix against the host's documentation.
Try it: Ship it and iterate
1. Deploy your recipe finder (or another project) to a free host and open the URL on your phone. 2. Make a small visible change (the header text), push it, and watch a new deployment appear. 3. Create a branch, change something else, push the branch and open the preview URL. Merge when you like it. 4. Add the live link and a screenshot to your README and to your resume's project section.
Key takeaways
npm run buildmakes adistfolder of static files; a static host just serves it.- Build and preview locally first; fix import capitalisation because hosts run Linux.
- Single-page apps need a rewrite to
index.html, or refreshing a deep link gives 404. - Connect GitHub to Vercel or Netlify for automatic deploys on every push and preview URLs for branches.
VITE_variables are public and fixed at build time; real secrets belong on a backend.- Polish the link before sharing: title, description, mobile check and a good README.
Check yourself
Finishing the quiz marks the lesson as done.
1. After deploying, the home page works but refreshing /recipe/52772 shows 404. What do you add?
2. Which setting is correct for a standard Vite project on Vercel or Netlify?
3. You put a private payment API key in VITE_PAYMENT_KEY. What is the problem?
4. Your build works on Windows but fails on the host with 'cannot find module ./menu.jsx', and the file is Menu.jsx. Why?
5. What is the benefit of connecting your GitHub repository to the host?
Saved on this device. Create a free account to keep it on every device.
Practise this with Sam
A real mock interview, out loud, with an honest scorecard. Free to start.