Skip to main content

Building for Production

This guide covers building VozCraft for production deployment. Vite provides highly optimized builds with automatic code splitting, minification, and asset optimization.

Quick Start

Build VozCraft for production with a single command:
npm run build
This creates an optimized production build in the dist/ directory, ready for deployment.
Build time: Typically 5-15 seconds on modern hardware.Output size: ~150-200 KB (gzipped) for the complete application.

Build Process

The build Script

The build command is defined in package.json:
package.json
{
  "scripts": {
    "build": "vite build"
  }
}
This executes Vite’s production build process.

Build Configuration

Vite uses vite.config.js for build settings:
vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
})
This is the minimal configuration. VozCraft uses Vite’s intelligent defaults for optimal production builds.

What Happens During Build

1

Dependency resolution

Vite analyzes all imports and dependencies:
// src/main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
All dependencies are traced and bundled.
2

JSX transformation

React JSX is compiled to JavaScript:
// Before (JSX)
<button onClick={handleClick}>
  {isPlaying ? 'Pause' : 'Play'}
</button>

// After (JavaScript)
React.createElement('button', {
  onClick: handleClick
}, isPlaying ? 'Pause' : 'Play')
3

Code minification

JavaScript is minified using esbuild:
// Before
const speak = useCallback((txt, vozLabel, generoLabel) => {
  window.speechSynthesis.cancel();
  const utterance = new SpeechSynthesisUtterance(txt);
  // ...
}, []);

// After (minified)
const s=useCallback((t,v,g)=>{window.speechSynthesis.cancel();const u=new SpeechSynthesisUtterance(t);...},[]);
Minification savings: Typically 40-60% size reduction.
4

Asset optimization

Images and other assets are optimized:
  • Images: Compressed and copied to dist/assets/
  • Fonts: Inlined or copied based on size
  • SVG: Minified and optimized
public/logo.png (5 KB) → dist/logo.png
public/logotipo.png (205 KB) → dist/logotipo.png
public/manifest.json → dist/manifest.json
5

CSS processing

Inline styles are extracted and optimized:
// Inline styles in App.jsx are preserved
<div style={{ 
  background: dark ? '#0a1628' : '#eef2fb',
  minHeight: '100vh' 
}}>
VozCraft uses inline styles exclusively, so no separate CSS file is generated.
6

Code splitting

Vite automatically splits code for optimal loading:
  • Vendor chunks: React and React-DOM in separate chunks
  • Dynamic imports: Lazy-loaded components (if any)
  • Entry point: Main application code
Example output:
dist/assets/index-abc123.js      (main app code)
dist/assets/vendor-def456.js     (React libraries)
7

Hash generation

Asset filenames include content hashes for cache busting:
index-abc123def456.js
vendor-789xyz.js
Cache busting: Hash changes when content changes, forcing browser cache refresh.

Build Output

Directory Structure

After building, the dist/ directory contains:
dist/
├── index.html                    # Entry HTML file
├── logo.png                      # App logo (5 KB)
├── logotipo.png                  # PWA icon (205 KB)
├── manifest.json                 # PWA manifest
├── icons.svg                     # Icon sprite
└── assets/
    ├── index-[hash].js           # Main application code
    └── index-[hash].css          # Extracted CSS (if any)

File Sizes

Typical production build sizes:
FileSize (uncompressed)Size (gzipped)
index.html~1 KB~500 B
index-[hash].js~450 KB~150 KB
React vendor~140 KB~45 KB
Total JavaScript~590 KB~195 KB
Images~210 KB~210 KB
Total~800 KB~405 KB
Gzip compression is typically applied by web servers (Nginx, Apache, CDNs) automatically. The gzipped size is what users actually download.

Optimized index.html

The built index.html includes minified content and hashed asset references:
dist/index.html
<!DOCTYPE html>
<html lang="es">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>VozCraft - TTS</title>
    <meta name="description" content="Texto a voz con IA - Genera audio realista en múltiples idiomas y voces" />
    <link rel="icon" type="image/png" href="/logo.png" />
    <link rel="manifest" href="/manifest.json" />
    <script type="module" crossorigin src="/assets/index-abc123.js"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
  1. Module script: Points to hashed bundle
  2. Crossorigin: Enables CORS for module loading
  3. Inline styles removed: Extracted to JS bundle
  4. Whitespace minified: Reduced HTML size

Advanced Build Configuration

Custom Vite Configuration

You can extend vite.config.js for custom build behavior:
vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  
  // Custom build options
  build: {
    // Output directory (default: dist)
    outDir: 'build',
    
    // Asset directory (default: assets)
    assetsDir: 'static',
    
    // Source maps for debugging
    sourcemap: true,
    
    // Minification (esbuild is default and fastest)
    minify: 'esbuild',
    
    // Target browsers
    target: 'es2015',
    
    // Chunk size warnings
    chunkSizeWarningLimit: 500,
    
    // Rollup options
    rollupOptions: {
      output: {
        // Manual chunk splitting
        manualChunks: {
          'react-vendor': ['react', 'react-dom'],
        },
      },
    },
  },
  
  // Base URL (for subdirectory hosting)
  base: '/',
})
Enable source maps for production debugging:
build: {
  sourcemap: true,  // or 'inline' or 'hidden'
}
Options:
  • true: Separate .map files
  • 'inline': Inline source maps (larger bundle)
  • 'hidden': Source maps without reference (for error tracking)
  • false: No source maps (smallest bundle)
Source maps expose your source code. Only enable in production if you need debugging or error tracking.

Environment-Specific Builds

Create different builds for different environments:
vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig(({ mode }) => {
  return {
    plugins: [react()],
    
    build: {
      sourcemap: mode === 'development',
      minify: mode === 'production' ? 'esbuild' : false,
    },
    
    define: {
      __APP_VERSION__: JSON.stringify(process.env.npm_package_version),
      __BUILD_DATE__: JSON.stringify(new Date().toISOString()),
    },
  }
})
Build with specific mode:
vite build --mode production
vite build --mode staging
vite build --mode development

Build Optimization Strategies

1. Code Splitting

Split large components into separate chunks:
src/App.jsx
import { lazy, Suspense } from 'react'

// Lazy load heavy components
const AudioPlayer = lazy(() => import('./components/AudioPlayer'))
const HistoryPanel = lazy(() => import('./components/HistoryPanel'))

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <AudioPlayer />
      <HistoryPanel />
    </Suspense>
  )
}
When to use code splitting:
  • Large third-party libraries
  • Route-based components (if using React Router)
  • Features used by < 50% of users
  • Heavy visualization components

2. Tree Shaking

Vite automatically removes unused code:
// Only the used functions are included in the build
import { useState, useEffect, useCallback } from 'react'
// useRef, useMemo, etc. are NOT included if unused
Tree shaking requirements:
  • Use ES modules (import/export)
  • Avoid CommonJS (require)
  • Use named imports when possible

3. Asset Optimization

Image Optimization

vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import imagemin from 'vite-plugin-imagemin'

export default defineConfig({
  plugins: [
    react(),
    imagemin({
      gifsicle: { optimizationLevel: 7 },
      optipng: { optimizationLevel: 7 },
      mozjpeg: { quality: 80 },
      svgo: {
        plugins: [
          { name: 'removeViewBox', active: false },
          { name: 'removeEmptyAttrs', active: false },
        ],
      },
    }),
  ],
})
Requires installing: npm install -D vite-plugin-imagemin

Font Loading

VozCraft loads Google Fonts. Consider self-hosting for better performance:
<!-- Current (external) -->
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=Sora:wght@700;800&display=swap" rel="stylesheet" />

<!-- Better (self-hosted) -->
<link rel="preload" href="/fonts/DMSans-Regular.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="/fonts/fonts.css">

4. Bundle Analysis

Analyze bundle size to identify optimization opportunities:
npm install -D rollup-plugin-visualizer
vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { visualizer } from 'rollup-plugin-visualizer'

export default defineConfig({
  plugins: [
    react(),
    visualizer({
      open: true,
      gzipSize: true,
      brotliSize: true,
    }),
  ],
})
After building, opens a treemap visualization showing bundle composition.

Build Scripts

Custom Build Scripts

Add specialized build scripts to package.json:
package.json
{
  "scripts": {
    "build": "vite build",
    "build:analyze": "vite build && vite-bundle-visualizer",
    "build:staging": "vite build --mode staging",
    "build:production": "vite build --mode production",
    "build:clean": "rm -rf dist && vite build",
    "build:gzip": "vite build && gzip -k dist/**/*.{js,css,html}"
  }
}
Remove old build before creating new one:
npm run build:clean
Ensures no stale files remain in dist/.

Performance Budgets

Set performance budgets to catch bundle bloat:
vite.config.js
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          // Warn if vendor chunk exceeds 500 KB
          if (id.includes('node_modules')) {
            return 'vendor'
          }
        },
      },
    },
    chunkSizeWarningLimit: 500, // KB
  },
})
If a chunk exceeds the limit, Vite shows a warning:
(!) Some chunks are larger than 500 KiB after minification.
Consider code splitting to reduce chunk sizes.

Testing the Build

Local Preview

Test the production build locally:
npm run build
npm run preview
This serves the dist/ directory at http://localhost:4173.
Preview server features:
  • Simulates production environment
  • Serves compressed files
  • Uses production URLs
  • Tests PWA functionality

Serve with Different Servers

cd dist
python3 -m http.server 8000
Visit http://localhost:8000

Build Checklist

1

Clean build

rm -rf dist
npm run build
Start with a clean slate.
2

Check build output

Verify:
  • No build errors or warnings
  • Bundle sizes are reasonable
  • All assets copied to dist/
3

Test locally

npm run preview
Test all functionality:
  • Speech synthesis works
  • Audio download works
  • History persists
  • PWA manifest loads
  • Dark/light theme works
4

Check console

Open DevTools and verify:
  • No JavaScript errors
  • No 404s for missing assets
  • No CSP violations
5

Test on mobile

  • Open preview URL on phone
  • Test PWA installation
  • Verify responsive design
  • Test touch interactions
6

Lighthouse audit

Run Lighthouse in Chrome DevTools:
  • Performance: 90+
  • Accessibility: 90+
  • Best Practices: 90+
  • SEO: 90+
  • PWA: Installable

Common Build Issues

Cause: Using features not supported by target browsers.Solution:
vite.config.js
build: {
  target: 'es2020',  // or newer
}
Or add polyfills for older browsers.
Cause: Incorrect asset paths or base URL.Solution:
vite.config.js
base: '/',  // or '/subpath/' if hosting in subdirectory
Use root-relative paths:
// Good
<img src="/logo.png" />

// Bad (won't work in subdirectory)
<img src="logo.png" />
Causes:
  • Large dependencies
  • Unused code not tree-shaken
  • Unoptimized images
Solutions:
  1. Analyze bundle:
npm run build:analyze
  1. Lazy load heavy components:
const HeavyComponent = lazy(() => import('./HeavyComponent'))
  1. Replace large dependencies with smaller alternatives
  2. Enable compression on server (gzip/brotli)
Solution:
export NODE_OPTIONS="--max-old-space-size=4096"
npm run build
Or in package.json:
"scripts": {
  "build": "node --max-old-space-size=4096 ./node_modules/.bin/vite build"
}

Continuous Integration

GitHub Actions

.github/workflows/build.yml
name: Build

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
      
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: dist
          path: dist/

Next Steps

Deployment

Deploy your build to production

PWA Setup

Optimize PWA configuration

Build docs developers (and LLMs) love