Deploy a React + Vite App to Production

Lint, type-check, and deploy a React app to a live URL with CI/CD.

Introduction

30 Second Summary

Your code works on your machine. But until it lives at a public URL with professional tooling behind it, no one else can see what you have built.

In this project, you will take your existing React + Vite application and transform it into a production-deployed web app with automated linting, type-safe components, and recruiter-ready documentation.

What You'll Build

You will configure production-grade ESLint rules, refactor a component to use TypeScript interfaces, and deploy your app to a live URL on Vercel with automatic CI/CD from GitHub.

By the end of this project, you'll have:

  • A production-grade ESLint flat config with TypeScript-strict and React Hooks rules catching bugs before they reach users.
  • A live deployment on Vercel with automatic deployments triggered by every push to GitHub.
  • A type-safe component architecture using interface-driven props and named exports that documents itself.
  • A recruiter-ready README documenting your tech stack decisions, architecture, and deployment workflow.
  • Secret Mission: Add a component architecture diagram and Lighthouse performance metrics to your README that showcases scores from your live deployment.

Are there any prerequisites?

No prior deployment experience is needed. This guide includes setup verification for all required tools, including Node.js, Git, and your Vercel account.

Verify Your Environment and Audit Project Health

Before making any changes to your codebase, you need to confirm your development environment is healthy. Think of it like checking your car before a road trip.

In this step, you will verify your tools are working, protect your project with a proper .gitignore, and install the linting packages that will act as your automated code reviewer.

In this step, get ready to:
  • Verify your Node.js, Git, and npm versions are accessible.
  • Audit and fix your .gitignore to protect sensitive files from version control.
  • Install ESLint packages for TypeScript and React Hooks linting.
Verify tooling versions and project dependencies

Your project depends on Node.js for running JavaScript tooling, Git for version control, and npm for managing packages. Confirming they are accessible in your terminal prevents confusing errors later.

  • Confirm your tool versions by running these commands in your terminal:
node -v
git --version
npm --version

What should I see?

Each command prints a version number. node -v should show v24 or higher. git --version and npm --version should each output a version number confirming they are installed.

Your project's package.json tracks which packages are installed. You can inspect the top-level packages and check for missing peer dependencies.

  • Inspect your installed packages by running:
npm ls --depth=0

What does this command do?

This lists every top-level package in your project. Look for any red UNMET PEER DEPENDENCY warnings in the output.

If you see warnings, fix them by running npm install before proceeding. This ensures your dependency tree is healthy before you add new packages.

Why separate dependencies from devDependencies?

Your package.json should distinguish between dependencies (packages your app needs at runtime, like react) and devDependencies (packages only needed during development, like eslint and typescript).

If you see linting or build tools in dependencies, move them to devDependencies. This keeps your production bundle lean.

Command not found?

If any version command fails, the tool is not in your system PATH. Make sure you installed Node.js from the official site and restarted your terminal after installation.

help me fix a 'command not found' error for node, git, or npm

Audit and fix .gitignore

A .gitignore file tells Git which files and folders to exclude from version control. Without it, you risk committing secrets, build output, or thousands of dependency files to your repository.

  • Open your .gitignore file in VS Code and replace its contents with the following:
node_modules
dist
.env
.env.local
.env.*.local
.vercel
*.log

Why does each entry matter?

  • node_modules contains thousands of dependency files that are reproduced via npm install. Never commit these.
  • dist is the build output folder. It gets regenerated on every deploy.
  • .env files contain secrets like API keys and tokens that must never be in version control.
  • .vercel contains project linking metadata specific to your machine.
  • *.log catches npm debug logs and other log files that clutter your repo.
  • Save .gitignore.
  • In the VS Code Explorer sidebar, confirm .gitignore shows the seven entries you just pasted.

Already committed node_modules before?

If you previously committed node_modules to Git, updating .gitignore alone will not remove it from tracking. You need to clear the cached tracking by running:

git rm -r --cached node_modules

This removes node_modules from Git's index without deleting the actual folder from your machine.

help me remove node_modules from Git tracking

✔️ Awesome, I've got everything!

Great. Double check you have saved your .gitignore file before moving on.

ⓧ I'd like to double check the full code

Here is the complete .gitignore file for comparison:

node_modules
dist
.env
.env.local
.env.*.local
.vercel
*.log
Install and verify ESLint dependencies for the flat config

Your project already has an eslint.config.js file, which is the modern flat config format. Now you need the packages that power TypeScript-aware and React Hooks-aware linting.

  • Install the ESLint packages as dev dependencies by running:
npm install --save-dev eslint @eslint/js typescript-eslint eslint-plugin-react-hooks

What does each package do?

  • eslint is the core linting engine that analyzes your code.
  • @eslint/js provides the base JavaScript recommended rules.
  • typescript-eslint adds TypeScript-specific rules that catch type-safety issues.
  • eslint-plugin-react-hooks enforces React Hooks rules so your Hooks are always called correctly.
  • Verify the packages appear in the devDependencies section of your package.json.
  • Confirm ESLint is working by running:
npx eslint --version

You should see a version number like v10.x.x printed in your terminal. If it outputs an error instead, the installation failed.

Seeing an error from npx eslint --version?

If the command is not recognized, try closing and reopening your terminal panel in VS Code. Sometimes the PATH does not refresh until a new terminal session starts.

If you see a dependency resolution error, delete your node_modules folder and package-lock.json, then run npm install again to get a clean install.

help me fix my ESLint installation

Your environment is verified and your project foundation is solid. Next up, you will configure ESLint with production-grade rules that catch bugs before they ever reach your live site.

Configure ESLint Flat Config for Production-Grade Linting

Your environment is verified, your .gitignore is protecting sensitive files, and your ESLint packages are installed. Now it's time to configure ESLint itself.

ESLint is your automated code reviewer. It catches bugs, enforces consistency, and ensures your React Hooks follow the rules of Hooks. In this step, you'll write a production-grade ESLint flat config, fix any violations it finds, and add convenient lint scripts to your project.

In this step, get ready to:
  • Replace your existing ESLint config with a production-grade flat config.
  • Run the linter and fix all violations in your source files.
  • Add lint scripts to your package.json for convenient repeated use.
Write the production ESLint flat config

Your project already has an eslint.config.js file. You'll replace its contents with a properly configured version that combines three layers of protection: base JavaScript rules, strict TypeScript rules, and React Hooks rules.

  • Open eslint.config.js in your project root.
  • Select all existing content and replace it with the following configuration:
import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
import reactHooks from 'eslint-plugin-react-hooks';

export default defineConfig([
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      js.configs.recommended,
      tseslint.configs.strict,
      tseslint.configs.stylistic,
    ],
    languageOptions: {
      parserOptions: {
        projectService: true,
      },
    },
    rules: {
      '@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
      '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
    },
  },
  reactHooks.configs.flat.recommended,
]);

What does this config do?

This config stacks three layers of protection using defineConfig from ESLint's flat config API:

  • js.configs.recommended catches common JavaScript mistakes (undeclared variables, duplicate keys, unreachable code).
  • tseslint.configs.strict is stricter than recommended and catches more potential type-safety bugs. tseslint.configs.stylistic enforces consistent TypeScript style patterns.
  • reactHooks.configs.flat.recommended enforces two critical rules: Hooks can only be called at the top level of a component, and useEffect dependency arrays must be complete.
  • projectService: true in parserOptions enables type-aware linting, which uses your TypeScript compiler for more powerful rules.
  • The files pattern restricts linting to only your .ts and .tsx source files.
  • Save eslint.config.js.

Seeing an import error when you save?

  • Make sure eslint.config.js is in your project root (the same folder as package.json). ESLint searches for the config starting from the current working directory.
  • Confirm you installed all four packages in Step 1: eslint, @eslint/js, typescript-eslint, and eslint-plugin-react-hooks. Run npm ls eslint-plugin-react-hooks to verify.
  • If VS Code's ESLint extension shows errors in the Output panel, try restarting the ESLint server: open the Command Palette with Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows) and run ESLint: Restart ESLint Server.

Still stuck? Help me troubleshoot my ESLint flat config. I replaced the file contents but I'm seeing import or module errors.

✔️ Awesome, I've got everything!

Great. Double check you've saved eslint.config.js before moving on.

ⓧ I'd like to double check the full code

Here is the complete eslint.config.js file. Compare your file against this:

import js from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
import reactHooks from 'eslint-plugin-react-hooks';

export default defineConfig([
  {
    files: ['**/*.{ts,tsx}'],
    extends: [
      js.configs.recommended,
      tseslint.configs.strict,
      tseslint.configs.stylistic,
    ],
    languageOptions: {
      parserOptions: {
        projectService: true,
      },
    },
    rules: {
      '@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
      '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
    },
  },
  reactHooks.configs.flat.recommended,
]);
Run the linter and fix violations

With your config saved, it's time to run ESLint against your source code. You will likely see errors. This is normal and expected.

  • Run the linter across your source files by running this command:
npx eslint src/

What errors will I see?

Common violations from the strict TypeScript config include:

  • @typescript-eslint/no-explicit-any means you have any types that need replacing with proper interfaces.
  • @typescript-eslint/no-unused-vars means a declared variable is never used. Remove it or prefix the parameter with an underscore (_unused) if it must stay.
  • react-hooks/exhaustive-deps means a useEffect dependency array is missing a variable that's referenced inside the effect.

Fix each error. The rule name (e.g., @typescript-eslint/no-explicit-any) is searchable in the typescript-eslint docs if you need help understanding a specific rule.

Never disable rules just to silence errors

Each rule exists to prevent a real class of bugs. If a specific line genuinely needs an exception, use a targeted inline comment:

// eslint-disable-next-line @typescript-eslint/no-explicit-any

Never disable a rule globally in your config just to make errors disappear.

  • Fix all errors reported by npx eslint src/.
  • Re-run npx eslint src/ until it exits with zero errors.

Errors you cannot figure out how to fix?

  • If you see a wall of errors, focus on one file at a time. Run npx eslint src/App.tsx to target a single file.
  • For no-explicit-any errors, replace any with the actual shape of your data. Start with unknown if unsure, then narrow it down.
  • For exhaustive-deps errors, add the missing variable to the dependency array. If adding it causes an infinite loop, extract the value with useCallback or useMemo.

Help me fix ESLint errors in my React TypeScript project. I'm using strict TypeScript rules and need help resolving specific violations.

Add lint scripts to package.json

Typing npx eslint src/ every time is tedious. Adding scripts to package.json lets you run linting with a short, memorable command.

  • Open package.json and find the "scripts" section.
  • Add (or update) these two entries inside "scripts":
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix"

What do these scripts do?

  • npm run lint runs the linter and reports all errors without changing any files.
  • npm run lint:fix runs the linter and automatically fixes issues that have safe, deterministic fixes (like removing unused imports or reordering properties).
  • Save package.json.
  • Confirm your lint script works by running:
npm run lint

The command should exit with no errors. If you have warnings (yellow text), those are non-blocking suggestions and are acceptable.

npm run lint shows errors you already fixed?

  • Make sure you saved all files you edited. Unsaved changes won't be picked up by the linter.
  • Double check your "lint" script value is exactly "eslint src/" with no typos. A missing space or wrong path will either lint nothing or the wrong directory.
  • If you see a "Could not find config file" error, ensure eslint.config.js exists in your project root (the same directory as package.json).

My npm run lint command is failing. Help me figure out why.

Your linter is locked and loaded. Every file in src/ now passes strict TypeScript and React Hooks rules. Next up, you'll refactor a component to demonstrate type-safe, interface-driven architecture.

Refactor a Component to Type-Safe Architecture

Your ESLint config is locked in and catching errors before they reach production. Now it's time to level up the code itself.

In this step, you'll refactor one of your existing components to follow a production-grade TypeScript pattern. Explicit Props interfaces make your components self-documenting. A recruiter or teammate reading your code can immediately see what a component accepts without reading the implementation.

In this step, get ready to:
  • Refactor a component to use explicit Props interfaces and named exports.
  • Organize related components into subdirectories with barrel exports.
  • Move static assets into proper locations for build-time optimization.
Create a type-safe component with an interface

Right now your components in src/components/ may accept props without a named type. This means TypeScript cannot catch incorrect usage at compile time.

  • Choose one of your existing components in src/components/.
  • If it currently has no TypeScript interface for its props (props typed as any, or inline without a named type), you'll refactor it now.

The production pattern for every React component has three rules:

  • Define a Props interface above the component.
  • Use that interface as the type for the component's parameter.
  • Export the component as a named export (not a default export).

Here is what the pattern looks like when applied to a component:

✔️ I'll give it a go!

Apply the three rules above to your chosen component. Define an interface for its props, type the component's parameter with that interface, and switch to a named export.

Once you're done, come back and verify it works.

ⓧ Maybe a little help...

Here is an example of a component refactored to follow the Props interface pattern. Use this as a reference when refactoring your own component.

// Define the Props interface above the component
interface CardProps {
  title: string;
  description: string;
  imageUrl?: string;
}

// Named export with typed props
export function Card({ title, description, imageUrl }: CardProps) {
  return (
    <div className="card">
      <h2>{title}</h2>
      <p>{description}</p>
      {imageUrl && <img src={imageUrl} alt={title} />}
    </div>
  );
}

What does this pattern do?

  • The interface declares every prop the component accepts, including which ones are optional (marked with ?).
  • TypeScript will now throw a compile-time error if any caller passes the wrong type or forgets a required prop.
  • The named export function ensures every file that imports this component uses the exact same name.

Apply this same structure to your chosen component now. Replace its default export with a named export, and add an explicit interface for all the props it receives.

Why named exports over default exports?

Named exports enforce consistent import names across your codebase. With default exports, one file can import the same component as Button, another as Btn, another as MyButton.

Named exports prevent this inconsistency. They also enable better IDE auto-imports and tree-shaking during the production build.

  • Save your refactored component file.
  • Verify your component passes the linter by running:
npx eslint src/components/

You should see zero errors in the output. If you see any @typescript-eslint violations, fix them before moving on.

Seeing TypeScript errors on your interface?

  • Make sure your interface name ends with Props (e.g., CardProps) and uses interface not type. Your ESLint config enforces @typescript-eslint/consistent-type-definitions which requires interface over type.
  • If you have unused props in the interface, remove them or prefix the parameter name with an underscore (e.g., _onClick).

Help me fix TypeScript interface errors in my React component

Organize shared UI components with barrel exports

If your src/components/ folder contains more than 5 files, grouping related components into subdirectories makes the project easier to navigate. A barrel file (index.ts) re-exports the public API of each folder so other parts of your app import from the folder path, not individual files.

  • Create a layout/ subdirectory inside src/components/ for page structure components like Header, Footer, or Sidebar.
  • Move the relevant component files into src/components/layout/.
  • Create an index.ts barrel file inside the layout/ folder with the following content:
// Re-export layout components for clean imports
export { Header } from './Header';
export { Footer } from './Footer';

What does a barrel file do?

Instead of importing from deep paths like import { Header } from '../components/layout/Header', consumers can import from the folder: import { Header } from '../components/layout'.

This simplifies imports and lets you reorganize files inside the folder without updating every import across your app.

Keep barrels one level deep

Do not create deeply nested barrel files (barrels that re-export from other barrels). This creates circular dependency risks and slows down bundling.

  • Save index.ts.
  • Update any existing imports in your app that reference the moved files to use the new folder path.

Your src/components/ui/ folder (set up by shadcn/ui) already follows this pattern. You are extending the same approach to your custom components.

Imports breaking after moving files?

  • Check that your barrel file exports match the exact named exports from each component file.
  • Make sure you moved the actual .tsx files into the new subdirectory and did not leave copies behind.
  • Run npm run dev to see if the dev server reports any module resolution errors.

Help me fix broken imports after reorganizing my component folder structure

Move static assets into proper locations

Your public/ folder contains images. Where each image lives determines whether Vite can optimize it during the production build.

  • Images referenced in HTML (favicon, og:image) stay in public/. Vite copies these as-is without optimization.
  • Images imported by React components should move to src/assets/. Vite hashes and optimizes them during the build.

Why move images to src/assets/?

When an image is in src/assets/, Vite adds a unique hash to the filename at build time (e.g., logo.a1b2c3.png). This is called cache-busting.

If you update the image later, the hash changes and browsers automatically load the new version. Without hashing, users can see stale cached images indefinitely.

  • Identify any images in public/ that you import or reference in your JSX components (not in index.html).
  • Move those images to src/assets/ (create the folder if it does not exist).
  • Update the component that uses the image to import it at the top of the file:
// Import the image so Vite can hash and optimize it
import heroImage from '../assets/hero.jpg';
  • Use the imported variable as the src value in your JSX:
// Use the imported image reference in JSX
<img src={heroImage} alt="Hero banner" />

What about images that stay in public/?

Images that stay in public/ are referenced with an absolute path from root (e.g., /my-image.jpg). Use this for HTML metadata images like favicons and Open Graph images that are not referenced in JSX.

  • Save all modified files.
  • Verify everything renders correctly by running:
npm run dev

Open the URL shown in your terminal (typically http://localhost:5173) and confirm your images load and your refactored component renders correctly.

  • Run the linter one final time to confirm everything passes:
npx eslint src/

You should see zero errors across all your source files.

Images not loading after the move?

  • Check that your import path matches the actual location of the file in src/assets/.
  • Make sure you are not still using a hardcoded path like /hero.jpg in JSX. Use the imported variable instead.
  • Confirm that your project has a vite-env.d.ts file with /// <reference types="vite/client" /> so TypeScript recognizes image imports.

Help me fix image imports in my Vite React TypeScript project

Your codebase now has a professional structure with typed components, organized folders, and optimized assets. Next up, you'll run a production build and verify everything compiles cleanly before deploying to a live URL.

Run a Production Build and Verify Locally

Your components are refactored with proper TypeScript interfaces, and ESLint confirms zero errors across your source files. The next question: will your code survive the jump from development to production?

In this step, you'll compile, bundle, and preview your app exactly as it will appear to real users on the internet. This catches an entire class of bugs that only surface in production builds.

In this step, get ready to:
  • Run the TypeScript type checker to confirm zero errors across all files.
  • Run the production build to generate optimized static files.
  • Preview the production build locally to verify correctness before deploying.
Run the TypeScript compiler check

Your IDE catches type errors in open files, but it does not check your entire project at once. A full compiler check confirms there are zero TypeScript errors across every file before you attempt a build.

  • Run the TypeScript compiler in check-only mode:
npx tsc --noEmit

What does this command do?

The --noEmit flag tells TypeScript to check all your types without producing any output files.

This catches errors that ESLint cannot see: incorrect function return types, missing properties on objects passed between components, and incompatible library typings.

If the command exits with no output, you have zero type errors. If you see errors, fix each one before proceeding.

Seeing type errors?

Common errors at this stage:

  • "Property 'X' does not exist on type 'Y'" means you are accessing a property that is not in the interface. Fix the interface or remove the access.
  • "Type 'string | undefined' is not assignable to type 'string'" means you need a null check or a default value.
  • If you see many errors from node_modules, add "skipLibCheck": true to the compilerOptions in your tsconfig.json.

Fix all type errors before moving to the next substep. A single type error will cause the production build to fail.

Help me fix my TypeScript errors

Run the production build

The production build compiles your TypeScript, bundles all your code with Vite, tree-shakes unused code, minifies the output, and splits it into optimized chunks.

  • Run the build command:
npm run build

What does this command do?

This runs your package.json build script, which typically executes tsc -b && vite build. It compiles TypeScript, then Vite bundles everything into optimized static files in the dist/ folder.

The dist/ folder is what gets deployed to production. It contains no TypeScript, no source maps (by default), and no node_modules. It is a pure static site that any CDN can serve.

  • After a successful build, confirm the output files exist by running:
ls dist

You should see an index.html file and an assets/ folder containing hashed .js and .css files.

Build failed?

  • If you see TypeScript errors, go back and run npx tsc --noEmit to identify and fix them first.
  • If you see "Cannot find module" errors, run npm install to ensure all dependencies are present.
  • If you see import path errors, check that your file paths use the correct casing. File systems on macOS are case-insensitive, but the build is case-sensitive.

Help me debug my Vite production build failure

Preview the production build locally

The dev server (npm run dev) uses unbundled ESM modules and includes development-only features. The preview server shows exactly what production will look like.

Bugs can exist in one environment but not the other. For example, environment variables that are undefined in production, or dynamic imports that fail after bundling.

  • Serve your production build locally by running:
npx vite preview

Why preview separately from dev?

The dev server hot-reloads modules individually and injects development helpers. The preview server serves the exact static files from dist/ the same way a production CDN would.

Catching rendering issues here saves you from deploying a broken site and then scrambling to fix it live.

  • Open your browser to http://localhost:4173.
  • Verify the page renders correctly and images load.
  • Open DevTools with F12 and check the Console tab for any errors or warnings.
  • Test any navigation links to confirm they work.

Page not loading at localhost:4173?

  • Make sure you ran npm run build successfully before running preview. The preview command needs the dist/ folder to exist.
  • If port 4173 is in use, Vite will try the next available port. Check the terminal output for the actual URL.
  • If images are broken, check that your image paths are not hardcoded with http://localhost:5173. Always use relative paths or Vite imports.

Help me fix my Vite preview

  • Press Ctrl+C in your terminal to stop the preview server when you are done checking.

Your production build is verified and working locally. Next up, you'll deploy it to a live public URL with automatic CI/CD from GitHub.

Deploy to Vercel via GitHub

Your production build works perfectly on your local machine. Every page renders, images load, and the console is clean.

Now it's time to make your app accessible to anyone with a browser. In this step, you'll push to GitHub and deploy to Vercel for a live public URL with automatic redeployment on every push.

In this step, get ready to:
  • Create a vercel.json for SPA routing.
  • Push your project to GitHub.
  • Deploy to Vercel with automatic CI/CD.
Create a vercel.json for SPA routing and push to GitHub

Single-page apps handle routing in the browser with JavaScript. Without server-side configuration, navigating directly to any route other than / returns a 404 error because the server tries to find a file that does not exist.

  • Create a new file called vercel.json in your project root (the same folder that contains package.json) and paste the following configuration:
{
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ]
}

What does this configuration do?

The rewrites array tells Vercel to route all incoming requests to index.html. This lets your React app's client-side router handle the URL instead of the server looking for a matching file.

  • Save vercel.json.

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

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

Now push your entire project to GitHub so Vercel can access it.

  • Stage all files and commit by running these commands:
git add .
git commit -m "feat: production-ready refactor with ESLint, typed components, and Vercel config"

Conventional commit messages

Prefixes like feat:, fix:, and docs: follow the Conventional Commits standard. Recruiters reviewing your GitHub history can see you follow team conventions.

  • Set your branch to main, add the remote, and push by running:
git branch -M main
git remote add origin [[GITHUB_REPO_URL="https://github.com/your-username/your-repo-name.git"]]
git push -u origin main

What do these commands do?

  • git branch -M main renames your current branch to main (the standard default branch name).
  • git remote add origin links your local project to the GitHub repository.
  • git push -u origin main uploads your code and sets main as the default upstream branch for future pushes.
  • After the push completes, you should see output confirming the branch was pushed to GitHub successfully.

Seeing "fatal: remote origin already exists"?

This means your project already has a remote configured. Update the existing remote URL instead by running:

git remote set-url origin [[GITHUB_REPO_URL="https://github.com/your-username/your-repo-name.git"]]
git push -u origin main

If you see authentication errors, make sure you are signed into GitHub in your terminal. Help me fix my git push authentication issue

Connect GitHub to Vercel and deploy

Vercel's GitHub integration creates a webhook that triggers a new deployment every time you push code. Once connected, you never manually deploy again.

  • Open your browser and go to vercel.com/new.
  • Sign in with your GitHub account if prompted.
  • Under Import Git Repository, find your repository in the list and click Import.
  • Vercel auto-detects your framework as Vite and pre-fills the build settings.
  • Confirm the settings match these values:
  • Framework Preset: Vite (auto-detected).
  • Build Command: npm run build (auto-detected).
  • Output Directory: dist (auto-detected).
  • Install Command: npm install (auto-detected).
  • Click Deploy.

Vercel clones your repo, installs dependencies, runs the build, and deploys the output to a global CDN. Within 1-2 minutes, you will have a live URL in the format your-project-name.vercel.app.

What happens on every future push?

Vercel's GitHub integration creates a webhook. Every push to main triggers a production deployment. Every push to any other branch creates a preview deployment with its own unique URL.

This is CI/CD (Continuous Integration / Continuous Deployment) without you configuring a single pipeline file.

What about costs?

The Hobby plan gives you 100 deployments per day, 100 GB bandwidth, and 45 minutes max build time. A typical Vite build takes 15-30 seconds. You will not hit these limits for a personal portfolio project.

Don't see your repository in the list?

Click Configure GitHub App and grant Vercel access to your repository. If your repo belongs to an organization, you need Owner or Member access to import it.

Help me connect my GitHub repository to Vercel

Verify the live deployment

Your deployment should now be live. Time to confirm everything works on the public URL just like it did locally.

  • Click the deployment URL that Vercel provides on the project dashboard.
  • Verify the site loads and functions identically to your local preview.
  • Check that all images load correctly.
  • Open DevTools (press F12) and confirm the browser console shows no errors.
  • Test the CI/CD pipeline by making a small change (like editing any text in a component), then running:
git add .
git commit -m "docs: verify automatic deployment"
git push
  • Return to your Vercel dashboard. Within 60 seconds, you should see a new deployment building automatically.

Images not loading on the live site?

Check that your image paths are not hardcoded with http://localhost:5173. Always use relative paths or Vite imports for images in your components.

Images in public/ should be referenced with an absolute path from root (e.g., /my-image.jpg). Images in src/assets/ should be imported in your component file.

Help me fix images that aren't loading on my Vercel deployment

Secret mission

Portfolio-Ready README with Architecture Diagram and Performance Metrics

Your code is deployed and your linting is tight. But can you prove it to a recruiter who has 30 seconds to skim your GitHub? In this secret mission, you will write a portfolio-grade README with architecture reasoning, run a Lighthouse performance audit on your live deployment, and document the results.

Clean Up Your Resources

Clean Up Your Resources

This project uses free-tier services with no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Vercel project (free Hobby tier, no payment method required).
  • GitHub repository (free).

Keep everything running

No action needed. Choose this if you're still actively building or want to showcase your deployed portfolio site.

  • Your Vercel deployment stays live indefinitely on the free Hobby tier with 100 GB bandwidth per month.
  • Your GitHub repository remains public and accessible.
  • No charges will accrue for either service.

Pause - I'll come back to this later

Stop automatic deployments while keeping your project files and live URL intact.

  • In the Vercel dashboard, go to your project's Settings tab.
  • Select Git in the sidebar and disconnect the Git integration.
  • This stops automatic deployments on every push while keeping your existing live URL active.
  • To resume later, reconnect the Git integration from the same page.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Delete the Vercel project: In the Vercel dashboard, go to Project Settings > General, then scroll down and click Delete Project.
  • Delete the GitHub repository: Go to your repository on GitHub, click Settings, scroll to the bottom of the page, and select the option to delete the repository.
  • Your local project folder is unchanged by either deletion. If you also want to remove it, delete the folder from your Desktop or run the appropriate command for your operating system:

macOS

rm -rf ~/Desktop/your-project-folder

Windows

rmdir /s /q %USERPROFILE%\Desktop\your-project-folder

Nice Work!

Nice Work!

Nice work! You have taken a local React project and transformed it into a production-deployed, professionally documented web application with automated CI/CD.

You've learned how to:

  • Configured a production-grade ESLint flat config with typescript-eslint strict rules and React Hooks linting to catch bugs before they reach production.
  • Refactored components using the interface-driven Props pattern with named exports and barrel file organization for a scalable, type-safe codebase.
  • Deployed to a live public URL with automatic CI/CD via Vercel's GitHub integration, giving you production and preview deployments on every push.
  • Secret Mission: Created portfolio-grade documentation with architecture reasoning, Lighthouse performance metrics, and a complete getting-started guide for recruiters.

Ready to quiz yourself?