Build & Deployment

Advanced

Production build optimization, asset bundling, and deployment strategies for Web Engine applications.

Web Engine uses modern build tools (Vite, Turbo) for fast development and optimized production builds. This guide covers build configuration, asset optimization, and deployment to various hosting platforms.

Build System Overview#

Turborepo

Monorepo orchestration with intelligent caching

Vite

Lightning-fast dev server and optimized builds

Code Splitting

Automatic chunking for optimal loading

The monorepo structure uses Turborepo for build orchestration and Vite for application bundling:

turbo.json
json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"outputs": []
}
}
}

Production Builds#

Build Commands#

Terminal
bash
# Build all packages and apps
npm run build
# Build specific app
npm run build --filter=@web-engine-dev/studio
npm run build --filter=@web-engine-dev/player
npm run build --filter=@web-engine-dev/docs
# Build with specific packages
npm run build:packages
# Clean build cache
npm run clean

Vite Build Configuration#

vite.config.ts
typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { getChunkName } from "@web-engine-dev/build-config";
export default defineConfig({
plugins: [react()],
build: {
target: "es2020",
minify: "terser",
sourcemap: true,
rollupOptions: {
output: {
// Chunking strategy for optimal caching
manualChunks: (id) => {
return getChunkName(id);
},
// Asset naming
assetFileNames: "assets/[name]-[hash][extname]",
chunkFileNames: "chunks/[name]-[hash].js",
entryFileNames: "entries/[name]-[hash].js",
},
},
terserOptions: {
compress: {
drop_console: true, // Remove console.log
drop_debugger: true, // Remove debugger statements
pure_funcs: ["console.debug", "console.trace"],
},
},
},
// Asset optimization
assetsInlineLimit: 4096, // Inline assets < 4KB
// Dependencies to optimize
optimizeDeps: {
include: [
"three",
"bitecs",
"@dimforge/rapier3d-compat",
],
},
});

Code Splitting Strategy#

Web Engine uses a strategic chunking system defined in @web-engine-dev/build-config:

packages/build-config/src/chunking.ts
typescript
export const chunkingStrategy = {
vendor: [
"react",
"react-dom",
"three",
"@react-three/fiber",
"@react-three/drei",
"zustand",
],
physics: [
"@dimforge/rapier3d-compat",
"@react-three/rapier",
],
ui: [
"@radix-ui/react-slot",
"@radix-ui/react-dialog",
// ... all Radix UI components
],
};
export function getChunkName(id: string): string | null {
if (id.includes("node_modules")) {
if (chunkingStrategy.vendor.some(v => id.includes(v)))
return "vendor";
if (chunkingStrategy.physics.some(v => id.includes(v)))
return "physics";
if (chunkingStrategy.ui.some(v => id.includes(v)))
return "ui-libs";
return "deps";
}
return null;
}

Chunk Benefits

Strategic chunking enables:

  • Long-term caching of stable vendor code
  • Parallel loading of independent chunks
  • Smaller initial bundle size
  • Faster incremental updates

Asset Optimization#

Texture Compression#

TextureCompression.ts
typescript
import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
// Configure KTX2 loader for compressed textures
const ktx2Loader = new KTX2Loader();
ktx2Loader.setTranscoderPath("/basis/");
ktx2Loader.detectSupport(renderer);
// Configure DRACO loader for mesh compression
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("/draco/");
// Use in GLTF loader
gltfLoader.setKTX2Loader(ktx2Loader);
gltfLoader.setDRACOLoader(dracoLoader);

Model Optimization#

  • gltfpack - Compress GLTF/GLB files with Draco/Meshopt
  • Texture atlasing - Combine small textures into atlases
  • LOD generation - Create Level of Detail meshes
  • Mesh simplification - Reduce polygon count for distant objects
Terminal
bash
# Optimize GLTF with gltfpack
gltfpack -i model.gltf -o model.glb -cc
# Options:
# -cc: Compress meshes with Draco
# -tc: Compress textures with KTX2/Basis
# -si 0.5: Simplify meshes to 50% triangles

Environment Variables#

.env.production
bash
# API Configuration
VITE_API_URL=https://api.myengine.com
VITE_PLAYER_URL=https://play.myengine.com
VITE_DOCS_URL=https://docs.myengine.com
# Feature Flags
VITE_ENABLE_ANALYTICS=true
VITE_ENABLE_MULTIPLAYER=true
VITE_ENABLE_ASSET_STREAMING=true
# CDN Configuration
VITE_CDN_URL=https://cdn.myengine.com
VITE_ASSET_BASE_URL=https://assets.myengine.com
# Branding
VITE_ENGINE_NAME=MyEngine
VITE_ENGINE_VERSION=1.0.0
config.ts
typescript
// Access env variables
const config = {
apiUrl: import.meta.env.VITE_API_URL,
playerUrl: import.meta.env.VITE_PLAYER_URL,
enableAnalytics: import.meta.env.VITE_ENABLE_ANALYTICS === "true",
};

Deployment Targets#

Vercel (Recommended)#

vercel.json
json
{
"buildCommand": "turbo run build --filter=@web-engine-dev/studio",
"outputDirectory": "apps/studio/.next",
"framework": "nextjs",
"rewrites": [
{
"source": "/api/:path*",
"destination": "https://api.myengine.com/:path*"
}
],
"headers": [
{
"source": "/assets/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
]
}
Terminal
bash
# Deploy to Vercel
vercel --prod
# Deploy specific app
vercel --prod --cwd apps/studio
# Environment variables
vercel env add VITE_API_URL production

Netlify#

netlify.toml
toml
[build]
command = "turbo run build --filter=@web-engine-dev/player"
publish = "apps/player/dist"
[[redirects]]
from = "/api/*"
to = "https://api.myengine.com/:splat"
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"

Cloudflare Pages#

Terminal
bash
# Build command
turbo run build --filter=@web-engine-dev/docs
# Output directory
apps/docs/out
# Environment variables
NODE_VERSION=18

CDN Configuration#

For optimal performance, serve static assets from a CDN:

AssetCDN.ts
typescript
// Configure asset base URL
const ASSET_CDN = import.meta.env.VITE_CDN_URL || "";
export function getAssetUrl(path: string): string {
return `${ASSET_CDN}${path}`;
}
// Usage
const modelUrl = getAssetUrl("/models/character.glb");
const textureUrl = getAssetUrl("/textures/grass.ktx2");

CDN Best Practices

  • Use long cache times (1 year) for versioned assets
  • Enable compression (gzip/brotli) at the CDN level
  • Configure CORS headers for cross-origin requests
  • Use a CDN with global edge network for low latency

Production Checklist#

Pre-Deployment Checklist

  • Build Optimization
  • Enable minification and tree-shaking
  • Remove console.log statements
  • Generate sourcemaps for debugging
  • Enable asset compression (gzip/brotli)
  • Asset Optimization
  • Compress textures to KTX2/Basis format
  • Compress models with Draco/Meshopt
  • Generate LOD meshes for large models
  • Create texture atlases for UI elements
  • Performance
  • Test on low-end devices
  • Profile with Chrome DevTools
  • Ensure 60 FPS on target hardware
  • Monitor memory usage
  • Configuration
  • Set production API URLs
  • Configure CDN for assets
  • Enable analytics and error tracking
  • Test all environment variables
  • Security
  • Enable HTTPS everywhere
  • Configure CSP headers
  • Remove debug endpoints
  • Validate all user inputs

Production Monitoring#

Analytics.ts
typescript
// Track performance metrics
import { PerformanceMonitor } from "@web-engine-dev/logging";
const perfMonitor = new PerformanceMonitor(logger);
// Send metrics to analytics
function reportMetrics() {
const snapshot = perfMonitor.getSnapshot();
analytics.track("performance", {
fps: snapshot.fps.avg,
frameTime: snapshot.frameTime.avg,
heapUsed: snapshot.memory?.heapUsed,
});
}
// Report every 30 seconds
setInterval(reportMetrics, 30000);

Monitor key metrics in production:

Error Tracking

Sentry, LogRocket, or similar for error monitoring

Analytics

Track user behavior and performance metrics

Performance

Core Web Vitals, FPS, load times

Uptime

Monitor API and CDN availability

Docker Deployment#

Dockerfile
dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
# Install dependencies
COPY package*.json ./
COPY turbo.json ./
RUN npm ci
# Copy source
COPY . .
# Build
RUN npm run build --filter=@web-engine-dev/studio
# Production image
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/apps/studio/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
EXPOSE 3000
CMD ["npm", "start"]
docker-compose.yml
yaml
version: "3.8"
services:
studio:
build:
context: .
dockerfile: apps/studio/Dockerfile
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- VITE_API_URL=https://api.myengine.com
player:
build:
context: .
dockerfile: apps/player/Dockerfile
ports:
- "5173:80"
server:
build:
context: .
dockerfile: apps/server/Dockerfile
ports:
- "4000:4000"
environment:
- NODE_ENV=production
Advanced | Web Engine Docs | Web Engine Docs