Getting Started with Vite + React + TypeScript

Vite makes React development fast. TypeScript keeps things safe as your codebase grows. This guide shows how to scaffold a React app with Vite, tune TypeScript for maintainability, run locally with hot reloading, and ship a production build with Docker and Nginx. Along the way, we’ll include DevOps Notes, extra insights for building production-ready pipelines, monitoring, and deployments.


Creating the Project

We’ll bootstrap a React + TypeScript app using Vite’s official template.

First make sure the latest version of node is installed as as yarn.

nvm install lts/*
npm install -g yarn

Npm handles dependencies with deterministic installs and solid caching in CI. The result is a working React app with HMR in minutes.

npm create vite@latest vite-react-ts-example -- --template react-ts
cd vite-react-ts-example
yarn install
git init && git add -A && git commit -m "chore: init vite+react+ts"

You now have a React project with a src/ directory, TypeScript definitions, and Vite configured out of the box.

DevOps Notes

  • Run tsc --noEmit in your CI pipeline to catch type errors before deploy.
  • Adding ESLint + Prettier ensures consistency across teams; run them in pre-commit hooks (Husky) or CI.

Vite Configuration

Vite works without changes. Still, adding an alias and locking in the dev port helps local tooling and developer habits stay consistent.

vite.config.ts

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

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    strictPort: true
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  },
  build: {
    outDir: 'dist',
    sourcemap: true
  }
})

DevOps Notes

  • Fixing the dev server port helps when automating local environments (like Gitpod or DevContainers).
  • Consider environment-specific configs (vite.config.prod.ts) for production vs dev builds.

Running in Development Mode

In dev, speed matters. Vite’s HMR is instant—edit, save, see changes. We add a few scripts for development, building, and previewing the production bundle.

package.json (scripts)

{
  "scripts": {
    "dev": "vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview --port=4173"
  }
}

Run development:

yarn dev

Open http://localhost:5173 and start iterating. The feedback loop stays tight, which keeps teams productive.

DevOps Notes

  • Use .env.development and .env.production for environment-specific settings.
  • Add a healthcheck endpoint in dev containers so CI can confirm the app boots correctly.

Building for Production

Production builds are static assets. That’s perfect for CDNs and containers. Build locally, then preview to ensure the optimized bundle behaves as expected.

yarn build
yarn preview

You’ll find the compiled app in dist/. This is what we’ll ship with Nginx.

DevOps Notes

  • Run yarn build && yarn preview in CI to confirm artifacts before pushing images.
  • Store dist/ as a build artifact for debugging pipeline issues.

Nginx Configuration

SPAs need two things from Nginx: aggressive caching for hashed assets and a fallback to index.html for client-side routing. That’s exactly what this config does.

nginx.conf

server {
  listen 80;
  server_name _;

  root /usr/share/nginx/html;
  index index.html;

  location ~* \.(?:js|mjs|css|png|jpg|jpeg|gif|svg|webp|ico|woff2?)$ {
    try_files $uri =404;
    access_log off;
    add_header Cache-Control "public, max-age=31536000, immutable";
  }

  location / {
    try_files $uri /index.html;
    add_header Cache-Control "no-store";
  }
}

DevOps Notes

  • For observability, pipe Nginx logs to stdout/stderr (already default). Aggregate logs via ELK, Loki, or Datadog.
  • For metrics, add nginx-prometheus-exporter and scrape in Prometheus.

Dockerfile for Build and Deployment

A multi-stage Dockerfile keeps the runtime image small. The Node stage compiles the app. The Nginx stage serves only the static output—no dev dependencies or source code. This reduces attack surface and speeds up deploys.

Dockerfile

FROM node:22-alpine AS build
WORKDIR /app

COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

COPY . .
RUN yarn build

FROM nginx:1.27-alpine AS runtime
RUN rm -rf /usr/share/nginx/html/*

COPY --from=build /app/dist /usr/share/nginx/html
COPY config/nginx.conf /etc/nginx/conf.d/default.conf

EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Build it:

docker build -t vite-react-ts-example:latest .

Run:

docker run -p 8080:80 vite-react-ts-example:latest

Open http://localhost:8080 to test the production image locally.

DevOps Notes

  • Pin Node versions (node:22-alpine) to avoid future surprises.
  • Add USER nginx (non-root) to improve container security.
  • Scan images with trivy or grype to catch vulnerabilities early.
  • Use build cache mounts (--mount=type=cache) for faster Yarn installs in CI.

Docker Compose for Local Deployment

Compose gives you a single command to build and run the production image. It mirrors how you’ll run the app in staging or production, which reduces surprises.

docker-compose.yaml

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    image: vite-react-ts-nginx:latest
    ports:
      - "8080:80"
    restart: unless-stopped

Start it up:

docker compose up --build

Open http://localhost:8080 to test the production image locally.

DevOps Notes

  • Add a healthcheck to ensure the container is serving before marking it healthy.
  • In production, this service could be scaled behind a load balancer or deployed to Kubernetes.

Environment Variables

Vite exposes environment variables at build time when they start with VITE_. This approach works well for static SPAs, where you set values per environment, rebuild, and deploy.

Create .env:

VITE_API_BASE_URL=https://api.example.com

Use it in React:

const base = import.meta.env.VITE_API_BASE_URL

Rebuild the image when these change, since they’re compiled into the bundle.

DevOps Notes

  • For runtime config, serve a config.json from Nginx and load it at app start. This avoids rebuilding images for every environment.
  • Keep secrets out of .env—inject them with your CI/CD or orchestrator.

Added all this to a github repo: https://github.com/markcallen/vite-react-ts-example

Why This Stack Works

React gives you a powerful component model. Vite keeps the dev loop instant and the build blazing fast. TypeScript adds guardrails without slowing you down. Yarn ensures reproducible installs. Docker plus Nginx provides a small, stable, and portable runtime.

With DevOps practices layered in, including CI/CD, container security, observability, and environment management, you transition from a toy app to a production-ready pipeline. That’s the Everyday DevOps way: fast in dev, safe in prod.