Vite Bundler Setup for Web Project

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    822
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Vite Bundler Configuration for Web Project

Vite uses native ES modules in dev mode — files are served directly, without bundling. HMR works precisely: one file changes — one module updates. For production builds, it uses Rollup.

Result: cold start of dev server in 300–500 ms instead of 30–60 seconds with Webpack for large projects.

What's included in the work

Setting up vite.config.ts for React/Vue/Svelte, TypeScript, CSS/PostCSS, path aliases, backend API proxy, code splitting, environment variables, production optimizations.

Installation

# React + TypeScript
npm create vite@latest my-app -- --template react-ts
cd my-app && npm install

# or Vue
npm create vite@latest my-app -- --template vue-ts

# or Svelte
npm create vite@latest my-app -- --template svelte-ts

vite.config.ts — React project

import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react-swc'
import path from 'path'

export default defineConfig(({ mode }) => {
  const env = loadEnv(mode, process.cwd(), '')

  return {
    plugins: [
      react(),
    ],

    resolve: {
      alias: {
        '@': path.resolve(__dirname, './src'),
        '@components': path.resolve(__dirname, './src/components'),
        '@hooks': path.resolve(__dirname, './src/hooks'),
        '@utils': path.resolve(__dirname, './src/utils'),
        '@assets': path.resolve(__dirname, './src/assets'),
      },
    },

    server: {
      port: 3000,
      strictPort: true, // error if port is busy, instead of choosing another
      proxy: {
        '/api': {
          target: env.VITE_API_URL ?? 'http://localhost:8000',
          changeOrigin: true,
          rewrite: (p) => p.replace(/^\/api/, ''),
        },
        '/ws': {
          target: env.VITE_WS_URL ?? 'ws://localhost:8000',
          ws: true,
        },
      },
      hmr: {
        overlay: true,
      },
    },

    preview: {
      port: 4000,
      strictPort: true,
    },

    css: {
      modules: {
        localsConvention: 'camelCaseOnly',
      },
      preprocessorOptions: {
        scss: {
          additionalData: `@import "@/styles/variables.scss";`,
        },
      },
    },

    build: {
      target: 'es2020',
      outDir: 'dist',
      sourcemap: mode !== 'production',
      minify: 'esbuild',
      rollupOptions: {
        output: {
          manualChunks: {
            'vendor-react': ['react', 'react-dom'],
            'vendor-router': ['react-router-dom'],
            'vendor-query': ['@tanstack/react-query'],
            'vendor-ui': ['@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu'],
          },
          chunkFileNames: 'assets/js/[name]-[hash].js',
          entryFileNames: 'assets/js/[name]-[hash].js',
          assetFileNames: 'assets/[ext]/[name]-[hash].[ext]',
        },
      },
      // do not output chunk size warnings in CI
      chunkSizeWarningLimit: 600,
    },

    optimizeDeps: {
      include: [
        'react',
        'react-dom',
        'react-router-dom',
        '@tanstack/react-query',
      ],
    },

    define: {
      __APP_VERSION__: JSON.stringify(process.env.npm_package_version),
    },
  }
})

tsconfig.json — aliases

Aliases need to be described in both vite.config.ts and tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "strict": true,
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@hooks/*": ["src/hooks/*"],
      "@utils/*": ["src/utils/*"]
    },
    "types": ["vite/client"]
  }
}

Environment variables

# .env.development
VITE_API_URL=http://localhost:8000
VITE_APP_NAME=MyApp Dev

# .env.production
VITE_API_URL=https://api.example.com
VITE_APP_NAME=MyApp

Only variables with VITE_ prefix end up in browser code:

// auto-typing
/// <reference types="vite/client" />

interface ImportMetaEnv {
  readonly VITE_API_URL: string
  readonly VITE_APP_NAME: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

// usage
const apiUrl = import.meta.env.VITE_API_URL

Plugins for various tasks

npm install -D vite-plugin-svgr              # SVG → React components
npm install -D @vitejs/plugin-legacy          # legacy browser support
npm install -D vite-plugin-pwa               # Progressive Web App
npm install -D rollup-plugin-visualizer      # bundle analysis
npm install -D vite-plugin-checker           # TypeScript + ESLint in dev
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import svgr from 'vite-plugin-svgr'
import legacy from '@vitejs/plugin-legacy'
import { VitePWA } from 'vite-plugin-pwa'
import { visualizer } from 'rollup-plugin-visualizer'
import checker from 'vite-plugin-checker'

export default defineConfig({
  plugins: [
    react(),

    svgr({
      svgrOptions: { exportType: 'named', ref: true },
    }),

    checker({
      typescript: true,
      eslint: { lintCommand: 'eslint src --ext .ts,.tsx' },
    }),

    VitePWA({
      registerType: 'autoUpdate',
      manifest: {
        name: 'My App',
        short_name: 'App',
        theme_color: '#1a1a2e',
        icons: [
          { src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
          { src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
        ],
      },
    }),

    process.env.ANALYZE && visualizer({
      open: true,
      gzipSize: true,
      brotliSize: true,
      filename: 'dist/bundle-stats.html',
    }),
  ].filter(Boolean),
})

Code splitting via lazy

// src/router.tsx
import { lazy, Suspense } from 'react'
import { createBrowserRouter } from 'react-router-dom'

const ProductsPage = lazy(() =>
  import('./pages/ProductsPage')
  // Rollup hint for chunk name
  // import(/* rollupOptions: { name: "products" } */ './pages/ProductsPage')
)

export const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      {
        path: 'products',
        element: (
          <Suspense fallback={<PageSkeleton />}>
            <ProductsPage />
          </Suspense>
        ),
      },
    ],
  },
])

Static assets

// import with typing
import logoUrl from '@assets/logo.svg?url'         // string URL
import LogoComponent from '@assets/logo.svg?react'  // React component (svgr)
import rawSvg from '@assets/icon.svg?raw'           // raw SVG as string

// URL for web worker
import MyWorker from './workers/heavy.worker?worker'
const worker = new MyWorker()

Multi-target build (lib mode)

If project is a UI library or shared package:

export default defineConfig({
  build: {
    lib: {
      entry: path.resolve(__dirname, 'src/index.ts'),
      name: 'MyLib',
      formats: ['es', 'cjs'],
      fileName: (format) => `index.${format}.js`,
    },
    rollupOptions: {
      external: ['react', 'react-dom'],
      output: {
        globals: { react: 'React', 'react-dom': 'ReactDOM' },
      },
    },
  },
})

Scripts

{
  "scripts": {
    "dev": "vite",
    "build": "tsc --noEmit && vite build",
    "build:analyze": "ANALYZE=true vite build",
    "preview": "vite preview",
    "lint": "eslint src --ext .ts,.tsx --report-unused-disable-directives"
  }
}

tsc --noEmit before vite build — check types before building. Vite transpiles TypeScript without type checking, so this is important.

What we do

We configure vite.config.ts for the project stack (React/Vue/Svelte), aliases, backend proxy, CSS/SCSS, add necessary plugins, optimize manual chunks for application characteristics, configure environment variables, add bundle visualizer.

Timeline: a few hours for new project, 1 day when migrating from webpack.