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:
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:
{
"scripts" : {
"build" : "vite build"
}
}
This executes Vite’s production build process.
Build Configuration
Vite uses vite.config.js for build settings:
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
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.
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' )
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.
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
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.
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)
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:
File Size (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:
<! 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 >
Key changes from development
Module script : Points to hashed bundle
Crossorigin : Enables CORS for module loading
Inline styles removed : Extracted to JS bundle
Whitespace minified : Reduced HTML size
Advanced Build Configuration
Custom Vite Configuration
You can extend vite.config.js for custom build behavior:
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: '/' ,
})
Source Maps
Output Directory
Base URL
Browser Targets
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.
Change the build output directory: build : {
outDir : 'build' , // default: 'dist'
}
Useful for CI/CD pipelines that expect specific directory names. Set base URL for subdirectory hosting: base : '/vozcraft/' , // default: '/'
For hosting at https://example.com/vozcraft/ All asset URLs will be prefixed with /vozcraft/
Define minimum browser support: build : {
target : 'es2015' , // ES6 (2015)
// or: ['es2020', 'edge88', 'firefox78', 'chrome87', 'safari14']
}
Older targets = larger bundles (more polyfills)
Environment-Specific Builds
Create different builds for different environments:
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:
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
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
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:
{
"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}"
}
}
Clean Build
Analyze Build
Staging Build
Remove old build before creating new one: Ensures no stale files remain in dist/. Build and analyze bundle size: Opens bundle analyzer in browser. Build for staging environment: Uses .env.staging configuration.
Set performance budgets to catch bundle bloat:
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
Python HTTP Server
Node http-server
Nginx
cd dist
python3 -m http.server 8000
Visit http://localhost:8000 npx http-server dist -p 8000
Visit http://localhost:8000 server {
listen 80 ;
server_name localhost;
root /path/to/vozcraft/dist;
index index.html;
location / {
try_files $ uri $ uri / /index.html;
}
}
Build Checklist
Clean build
rm -rf dist
npm run build
Start with a clean slate.
Check build output
Verify:
No build errors or warnings
Bundle sizes are reasonable
All assets copied to dist/
Test locally
Test all functionality:
Speech synthesis works
Audio download works
History persists
PWA manifest loads
Dark/light theme works
Check console
Open DevTools and verify:
No JavaScript errors
No 404s for missing assets
No CSP violations
Test on mobile
Open preview URL on phone
Test PWA installation
Verify responsive design
Test touch interactions
Lighthouse audit
Run Lighthouse in Chrome DevTools:
Performance: 90+
Accessibility: 90+
Best Practices: 90+
SEO: 90+
PWA: Installable
Common Build Issues
Build fails with syntax errors
Cause: Using features not supported by target browsers.Solution: build : {
target : 'es2020' , // or newer
}
Or add polyfills for older browsers.
Assets not found after build
Cause: Incorrect asset paths or base URL.Solution: 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:
Analyze bundle:
Lazy load heavy components:
const HeavyComponent = lazy (() => import ( './HeavyComponent' ))
Replace large dependencies with smaller alternatives
Enable compression on server (gzip/brotli)
Out of memory during build
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