π₯ 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
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
π Folder Structure Preview
my-app/
ββ src/
β ββ App.tsx
β ββ main.tsx
β ββ assets/
ββ public/
ββ index.html
ββ vite.config.ts
π§ 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 thedist
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/, ''),
},
},
},
})
π 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))
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
Then in your code:
const apiUrl = import.meta.env.VITE_API_BASE_URL;
fetch(`${apiUrl}/users`);
π 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
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/, ''),
},
},
},
})
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"
}
}
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()
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"
}
}
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"]
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"]
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
β Usage
π§ 1. Start the Dev Stack
docker-compose up --build
- Frontend: http://localhost:5173
- Backend: http://localhost:3000/api
βοΈ 2. Make a test route in NestJS
server/src/app.controller.ts
@Get('hello')
getHello(): string {
return 'Hello from NestJS';
}
Then test in your React app:
useEffect(() => {
fetch('/api/hello')
.then(res => res.text())
.then(console.log)
}, []);
π¦ 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:
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)