DEV Community

Cover image for Vite for React SPA
Taki
Taki

Posted on

Vite for React SPA

πŸ”₯ Why Vite is Great for SPA React Projects

1. Blazing Fast Dev Experience

  • ESM-based Hot Module Replacement (HMR): Vite uses native ES Modules, so dev server startup is instant, and code changes reflect nearly instantly.
  • Unlike older tools like Create React App (CRA), Vite doesn't bundle everything upfrontβ€”only what’s needed.

2. Minimal Config, Extensible When Needed

  • Out of the box: βœ… JSX support, βœ… TypeScript, βœ… CSS Modules/PostCSS, βœ… Environment variables.
  • Need more? It uses Rollup under the hood for production, so you can customize or add plugins easily.

3. First-Class TypeScript + React Support

npm create vite@latest my-app --template react-ts
Enter fullscreen mode Exit fullscreen mode

It scaffolds a clean project in seconds. You’re ready to go with full TypeScript and React setup.

4. Lightweight & SPA-Ready

  • Vite is perfect for SPAs because:

    • It supports React Router easily.
    • You don’t get the overhead of a backend framework like Next.js or Remix.
    • You can control routing and deployment exactly how you want (e.g., with static hosting on Vercel, Netlify, etc.).

5. Built-in Features

  • Code splitting
  • Tree shaking
  • Built-in dev server
  • Easy static asset handling

βš–οΈ When to Choose Vite Over Next.js or CRA

Use Case Choose Vite
Pure SPA (no SSR or API layer) βœ… Best Choice
Want fast startup & dev speed βœ… Ideal
Full control over routing/build βœ… Preferable
Don’t need server-side rendering βœ… Good fit
Need backend/API integration ❌ Consider NestJS + Vite or Next.js

If you want a frontend-only app that fetches from APIs (like a NestJS backend), Vite is excellent.


πŸ’‘ Example: Create Vite + React + TypeScript Project

npm create vite@latest my-app --template react-ts
cd my-app
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

πŸ“ Folder Structure Preview

my-app/
β”œβ”€ src/
β”‚  β”œβ”€ App.tsx
β”‚  β”œβ”€ main.tsx
β”‚  └─ assets/
β”œβ”€ public/
β”œβ”€ index.html
β”œβ”€ vite.config.ts
Enter fullscreen mode Exit fullscreen mode

🧠 Best Practices with Vite in SPA

  • Use React Router DOM for client-side routing.
  • Use .env files for API keys, base URLs.
  • Keep Vite’s config minimal unless you need advanced Rollup behavior.
  • For production: run vite build and deploy the dist folder to static hosting.

⚠️ When Vite isn't Ideal

If you need:

  • SSR (Server-side rendering) β†’ Use Next.js
  • Incremental static regeneration (ISR) or hybrid SSR β†’ Use Next.js
  • Backend + frontend in one β†’ Use NestJS + Vite with Vite as frontend

βœ… Verdict

Yes, Vite is arguably the best option today for building a modern, fast, and clean React SPA if:

  • You don’t need SSR.
  • You want fast builds and DX.
  • You’re building a frontend-only client (e.g., talking to a NestJS API).

vite.config.ts for a React SPA using Vite that proxies API requests to a NestJS backend (which might run at http://localhost:3000).


πŸ›  Vite Proxy Config for NestJS Backend

βœ… Goals:

  • Vite runs on http://localhost:5173
  • NestJS runs on http://localhost:3000
  • All requests to /api are proxied to NestJS
  • Avoid CORS issues in development

πŸ“„ vite.config.ts

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173, // Frontend port
    proxy: {
      '/api': {
        target: 'http://localhost:3000', // NestJS backend
        changeOrigin: true,
        rewrite: path => path.replace(/^\/api/, ''),
      },
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

πŸ“ Frontend Call Example (e.g., using Axios or Fetch)

// This call from React (port 5173) will be proxied to NestJS (port 3000)
fetch('/api/users')
  .then(res => res.json())
  .then(data => console.log(data))
Enter fullscreen mode Exit fullscreen mode

Behind the scenes:
/api/users β†’ proxied to β†’ http://localhost:3000/users


πŸ’‘ Bonus: Handling Environment Variables

Add a .env file at the root of your Vite project:

VITE_API_BASE_URL=/api
Enter fullscreen mode Exit fullscreen mode

Then in your code:

const apiUrl = import.meta.env.VITE_API_BASE_URL;
fetch(`${apiUrl}/users`);
Enter fullscreen mode Exit fullscreen mode

πŸ”’ Don't forget for Production

In production:

  • You need to handle the /api routing via reverse proxy (e.g., Nginx, Vercel rewrites, or within your backend).
  • Or configure your frontend to use the full backend URL directly if they're deployed separately.

The full NestJS + Vite Docker dev setup as well:


complete Docker-based development setup for a fullstack Vite (React) + NestJS app. This includes:

  • βœ… Yarn workspaces or separate folders
  • βœ… Live reload in both apps
  • βœ… Vite proxy to NestJS for /api
  • βœ… Docker + docker-compose dev setup

πŸ—οΈ Folder Structure (Monorepo or Parallel)

You can go with one of the following:

Option A – Monorepo-style:

fullstack-app/
β”œβ”€β”€ client/       # Vite + React
β”œβ”€β”€ server/       # NestJS
β”œβ”€β”€ docker-compose.yml
└── README.md
Enter fullscreen mode Exit fullscreen mode

1️⃣ Vite (React) Frontend Setup

client/vite.config.ts

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    host: true, // Allow Docker access
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://server:3000', // use Docker service name
        changeOrigin: true,
        rewrite: path => path.replace(/^\/api/, ''),
      },
    },
  },
})
Enter fullscreen mode Exit fullscreen mode

client/package.json

{
  "name": "client",
  "version": "0.0.1",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@vitejs/plugin-react": "^4.0.0",
    "vite": "^5.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

2️⃣ NestJS Backend Setup

server/main.ts

import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.setGlobalPrefix('api') // Base path for APIs
  await app.listen(3000, '0.0.0.0')
}
bootstrap()
Enter fullscreen mode Exit fullscreen mode

server/package.json

{
  "name": "server",
  "version": "1.0.0",
  "scripts": {
    "start:dev": "nest start --watch"
  },
  "dependencies": {
    "@nestjs/common": "^10.0.0",
    "@nestjs/core": "^10.0.0",
    "@nestjs/platform-express": "^10.0.0",
    "reflect-metadata": "^0.1.13",
    "rxjs": "^7.8.1"
  },
  "devDependencies": {
    "@nestjs/cli": "^10.0.0",
    "@nestjs/schematics": "^10.0.0",
    "ts-node": "^10.9.1",
    "typescript": "^5.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

3️⃣ Docker Setup

client/Dockerfile

# --- React Dev ---
FROM node:18-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
EXPOSE 5173
CMD ["yarn", "dev"]
Enter fullscreen mode Exit fullscreen mode

server/Dockerfile

# --- NestJS Dev ---
FROM node:18-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install
COPY . .
EXPOSE 3000
CMD ["yarn", "start:dev"]
Enter fullscreen mode Exit fullscreen mode

Root docker-compose.yml

version: '3.8'

services:
  client:
    build:
      context: ./client
    ports:
      - "5173:5173"
    volumes:
      - ./client:/app
      - /app/node_modules
    depends_on:
      - server

  server:
    build:
      context: ./server
    ports:
      - "3000:3000"
    volumes:
      - ./server:/app
      - /app/node_modules
    command: yarn start:dev
Enter fullscreen mode Exit fullscreen mode

βœ… Usage

πŸ”§ 1. Start the Dev Stack

docker-compose up --build
Enter fullscreen mode Exit fullscreen mode

βš™οΈ 2. Make a test route in NestJS

server/src/app.controller.ts

@Get('hello')
getHello(): string {
  return 'Hello from NestJS';
}
Enter fullscreen mode Exit fullscreen mode

Then test in your React app:

useEffect(() => {
  fetch('/api/hello')
    .then(res => res.text())
    .then(console.log)
}, []);
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ Optional: Add MongoDB to Compose

Want to persist data? Add this to docker-compose.yml:

  mongo:
    image: mongo:6
    ports:
      - "27017:27017"
    volumes:
      - mongo-data:/data/db

volumes:
  mongo-data:
Enter fullscreen mode Exit fullscreen mode

Then in NestJS, connect to mongodb://mongo:27017.


🧠 Recap

Layer Tool Purpose
Frontend Vite Fast React SPA
Backend NestJS Modular API w/ TypeScript
Proxy Vite Dev /api β†’ NestJS
Runtime Docker Isolated Dev Envs

Top comments (0)