Skip to main content

Deployment Guide

VozCraft is a static web application that can be deployed to any static hosting platform. This guide covers deployment to popular hosting services including GitHub Pages, Vercel, Netlify, and traditional web servers.

Prerequisites

Before deploying, ensure you have:
1

Build the application

npm run build
This creates the dist/ directory with production-ready files.
2

Test the build locally

npm run preview
Verify everything works at http://localhost:4173
3

Verify build output

Check that dist/ contains:
  • index.html
  • manifest.json
  • assets/ directory with JS bundles
  • Images (logo.png, logotipo.png)
Build size: The complete VozCraft build is typically 400-800 KB (150-200 KB gzipped), making it fast to deploy and serve.

Vercel Deployment

Vercel provides zero-configuration deployment with automatic builds and global CDN.

Method 1: Vercel CLI

1

Install Vercel CLI

npm install -g vercel
2

Login to Vercel

vercel login
Follow the authentication prompts.
3

Deploy

vercel
Vercel will:
  • Detect the project as a Vite application
  • Build automatically
  • Deploy to a preview URL
4

Deploy to production

vercel --prod
Deploys to your production domain.

Method 2: Vercel Dashboard

1

Connect repository

  1. Go to vercel.com
  2. Click “Add New” → “Project”
  3. Import your GitHub/GitLab/Bitbucket repository
2

Configure build settings

Vercel auto-detects Vite projects. Verify settings:
  • Framework Preset: Vite
  • Build Command: npm run build
  • Output Directory: dist
  • Install Command: npm install
3

Deploy

Click “Deploy” to start the build and deployment.
Create vercel.json for custom configuration:
vercel.json
{
  "buildCommand": "npm run build",
  "outputDirectory": "dist",
  "devCommand": "npm run dev",
  "installCommand": "npm install",
  "framework": "vite",
  "rewrites": [
    { "source": "/(.*)", "destination": "/index.html" }
  ],
  "headers": [
    {
      "source": "/assets/(.*)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=31536000, immutable"
        }
      ]
    },
    {
      "source": "/(.*)\\.(?:jpg|jpeg|png|gif|ico|svg)",
      "headers": [
        {
          "key": "Cache-Control",
          "value": "public, max-age=86400, s-maxage=86400"
        }
      ]
    }
  ]
}
The rewrites rule ensures all routes serve index.html for SPA routing (if you add React Router later).
Vercel features:
  • ✅ Automatic builds on git push
  • ✅ Preview deployments for PRs
  • ✅ Global CDN (Edge Network)
  • ✅ Automatic HTTPS
  • ✅ Zero configuration
  • ✅ Free tier available

Netlify Deployment

Netlify offers similar features to Vercel with easy drag-and-drop deployment.

Method 1: Netlify CLI

1

Install Netlify CLI

npm install -g netlify-cli
2

Login to Netlify

netlify login
3

Initialize site

netlify init
Follow the prompts to create a new site or link existing one.
4

Deploy

netlify deploy --prod
Or for a draft deploy:
netlify deploy

Method 2: Netlify Dashboard

1

Connect repository

  1. Go to netlify.com
  2. Click “Add new site” → “Import an existing project”
  3. Connect your Git provider and select repository
2

Configure build

Set build configuration:
  • Base directory: (leave empty)
  • Build command: npm run build
  • Publish directory: dist
3

Deploy

Click “Deploy site” to start the build.

Method 3: Drag and Drop

1

Build locally

npm run build
2

Upload dist folder

  1. Go to Netlify Dashboard
  2. Drag and drop the dist/ folder onto the upload area
  3. Netlify deploys instantly
Create netlify.toml for custom configuration:
netlify.toml
[build]
  command = "npm run build"
  publish = "dist"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

[[headers]]
  for = "/assets/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
  for = "/*.js"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
  for = "/*.css"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
  for = "/manifest.json"
  [headers.values]
    Content-Type = "application/manifest+json"
    Cache-Control = "public, max-age=0, must-revalidate"
Netlify features:
  • ✅ Automatic builds on git push
  • ✅ Deploy previews for PRs
  • ✅ Global CDN
  • ✅ Automatic HTTPS
  • ✅ Form handling
  • ✅ Serverless functions
  • ✅ Free tier available

GitHub Pages Deployment

Deploy VozCraft directly from your GitHub repository. Create .github/workflows/deploy.yml:
.github/workflows/deploy.yml
name: Deploy to GitHub Pages

on:
  push:
    branches:
      - main

# Allow manual runs
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      
      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
      
      - name: Setup Pages
        uses: actions/configure-pages@v4
      
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v3
        with:
          path: './dist'
  
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v4
1

Enable GitHub Pages

  1. Go to repository Settings → Pages
  2. Source: “GitHub Actions”
2

Push workflow file

git add .github/workflows/deploy.yml
git commit -m "Add GitHub Pages deployment"
git push
3

Wait for deployment

GitHub Actions will automatically build and deploy.View progress: Actions tab in your repository
4

Access deployed site

Your site will be available at:
https://username.github.io/vozcraft/
Base URL configuration:If deploying to a repository page (not user/org page), update vite.config.js:
vite.config.js
export default defineConfig({
  plugins: [react()],
  base: '/vozcraft/',  // Replace with your repo name
})
This ensures assets load correctly from the subdirectory.

Method 2: gh-pages Package

1

Install gh-pages

npm install -D gh-pages
2

Add deploy script

Update package.json:
package.json
{
  "scripts": {
    "build": "vite build",
    "predeploy": "npm run build",
    "deploy": "gh-pages -d dist"
  }
}
3

Deploy

npm run deploy
This builds and pushes to the gh-pages branch.
4

Configure GitHub Pages

  1. Go to Settings → Pages
  2. Source: “Deploy from a branch”
  3. Branch: gh-pages/ (root)
  4. Save

Traditional Web Server Deployment

Nginx

Configuration for serving VozCraft on Nginx:
/etc/nginx/sites-available/vozcraft
server {
    listen 80;
    listen [::]:80;
    server_name vozcraft.com www.vozcraft.com;
    
    # Redirect to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name vozcraft.com www.vozcraft.com;
    
    # SSL configuration
    ssl_certificate /etc/letsencrypt/live/vozcraft.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/vozcraft.com/privkey.pem;
    
    # Document root
    root /var/www/vozcraft/dist;
    index index.html;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
    
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    
    # Cache static assets
    location /assets/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }
    
    # Cache images
    location ~* \.(jpg|jpeg|png|gif|ico|svg)$ {
        expires 30d;
        add_header Cache-Control "public";
    }
    
    # No cache for index.html
    location = /index.html {
        add_header Cache-Control "no-cache, must-revalidate";
    }
    
    # SPA routing - serve index.html for all routes
    location / {
        try_files $uri $uri/ /index.html;
    }
}
1

Upload build files

scp -r dist/* user@server:/var/www/vozcraft/dist/
Or use rsync:
rsync -avz --delete dist/ user@server:/var/www/vozcraft/dist/
2

Configure Nginx

sudo nano /etc/nginx/sites-available/vozcraft
sudo ln -s /etc/nginx/sites-available/vozcraft /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
3

Set up SSL (Let's Encrypt)

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d vozcraft.com -d www.vozcraft.com

Apache

Configuration for Apache web server:
/etc/apache2/sites-available/vozcraft.conf
<VirtualHost *:80>
    ServerName vozcraft.com
    ServerAlias www.vozcraft.com
    
    # Redirect to HTTPS
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</VirtualHost>

<VirtualHost *:443>
    ServerName vozcraft.com
    ServerAlias www.vozcraft.com
    
    DocumentRoot /var/www/vozcraft/dist
    
    # SSL configuration
    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/vozcraft.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/vozcraft.com/privkey.pem
    
    # Enable compression
    <IfModule mod_deflate.c>
        AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json
    </IfModule>
    
    # Cache static assets
    <Directory "/var/www/vozcraft/dist/assets">
        <IfModule mod_expires.c>
            ExpiresActive On
            ExpiresDefault "access plus 1 year"
        </IfModule>
    </Directory>
    
    # SPA routing
    <Directory "/var/www/vozcraft/dist">
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
        
        RewriteEngine On
        RewriteBase /
        RewriteRule ^index\.html$ - [L]
        RewriteCond %{REQUEST_FILENAME} !-f
        RewriteCond %{REQUEST_FILENAME} !-d
        RewriteRule . /index.html [L]
    </Directory>
</VirtualHost>
1

Enable required modules

sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2enmod expires
sudo a2enmod deflate
sudo a2enmod headers
2

Enable site

sudo a2ensite vozcraft
sudo apache2ctl configtest
sudo systemctl reload apache2

Cloud Storage Deployment

AWS S3 + CloudFront

1

Create S3 bucket

aws s3 mb s3://vozcraft-app
2

Enable static website hosting

aws s3 website s3://vozcraft-app/ \
  --index-document index.html \
  --error-document index.html
3

Upload build

aws s3 sync dist/ s3://vozcraft-app/ --delete
4

Set up CloudFront

  1. Create CloudFront distribution
  2. Origin: S3 bucket
  3. Default root object: index.html
  4. Custom error responses: 404 → /index.html (200)
5

Configure custom domain (optional)

  1. Create SSL certificate in ACM
  2. Add CNAME to CloudFront distribution
  3. Update Route 53 DNS

Deployment Checklist

1

Pre-deployment checks

☐ Run npm run build successfully☐ Test with npm run preview☐ Verify all features work☐ Check browser console for errors☐ Test on mobile devices☐ Run Lighthouse audit
2

Configuration

☐ Set correct base URL in vite.config.js☐ Update environment variables if needed☐ Configure caching headers☐ Set up SSL/HTTPS☐ Configure SPA routing fallback
3

Post-deployment

☐ Verify site loads correctly☐ Test PWA installation☐ Check all API features (speech synthesis, audio download)☐ Verify analytics (if configured)☐ Test from different browsers and devices☐ Check performance metrics

Performance Optimization

CDN Configuration

Optimal cache headers for different file types:
# HTML - no cache (always fresh)
Cache-Control: no-cache, must-revalidate

# JavaScript/CSS with hash - cache forever
Cache-Control: public, max-age=31536000, immutable

# Images - cache for 30 days
Cache-Control: public, max-age=2592000

# Manifest - short cache
Cache-Control: public, max-age=3600

Compression

Enable gzip and Brotli compression:
# Nginx
gzip on;
gzip_vary on;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/json;

# Brotli (requires module)
brotli on;
brotli_types text/plain text/css text/xml text/javascript application/javascript application/json;

Security Headers

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

Monitoring

Error Tracking

Integrate error tracking (optional):
src/main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'

// Error tracking
window.addEventListener('error', (event) => {
  console.error('Global error:', event.error);
  // Send to error tracking service
});

window.addEventListener('unhandledrejection', (event) => {
  console.error('Unhandled promise rejection:', event.reason);
  // Send to error tracking service
});

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

Analytics

Add analytics tracking:
index.html
<!-- Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'GA_MEASUREMENT_ID');
</script>

Rollback Strategy

Always maintain ability to rollback:

Vercel/Netlify

  • Automatic deployment history
  • Instant rollback via dashboard
  • Pin deployments to specific commits

Manual Deployments

# Tag releases
git tag v1.0.0
git push origin v1.0.0

# Keep previous builds
mv dist dist-$(date +%Y%m%d-%H%M%S)
npm run build

# Rollback if needed
rm -rf dist
mv dist-20260305-120000 dist

Common Deployment Issues

Cause: Server not configured for SPA routing.Solution: Configure fallback to index.html:
location / {
    try_files $uri $uri/ /index.html;
}
Cause: Incorrect base URL.Solution: Update vite.config.js:
base: '/correct-path/',  // or '/' for root
Cause: Missing CORS headers.Solution: Add CORS headers:
add_header Access-Control-Allow-Origin "*";

Next Steps

PWA Setup

Optimize PWA installation experience

Web Speech API

Understand the core technology

Build docs developers (and LLMs) love