Skip to content

Deployment & Publishing

This guide covers the full production pipeline: optimized builds, hosting on the web, packaging as a PWA for offline play, wrapping with Capacitor for app stores, and publishing to itch.io.

Production Build

bash
# Build all packages and your game app
pnpm build

# Build only your game (assumes packages are already built)
pnpm --filter my-game build

The build pipeline (@web-engine-dev/build) handles:

  • TypeScript compilation
  • Asset optimization (texture compression, audio transcoding, mesh quantization)
  • Code splitting by scene/feature
  • Tree-shaking of unused engine packages
  • Content hashing for long-term caching

Build Configuration

typescript
// game.build.config.ts
import { defineGameBuildConfig } from '@web-engine-dev/build';

export default defineGameBuildConfig({
  // Entry point
  entry: 'src/main.ts',
  outDir: 'dist',

  // Asset pipeline
  assets: {
    textures: {
      compress: true,
      formats: ['bc7', 'astc', 'etc2'], // generate per-platform formats
      maxSize: 2048,
      generateMipmaps: true,
    },
    audio: {
      formats: ['opus', 'mp3'], // opus for modern browsers, mp3 fallback
      quality: 0.7,
    },
    models: {
      draco: true, // geometry compression
      maxTextureSize: 1024,
    },
  },

  // Code splitting
  codeSplitting: {
    byScene: true, // each scene loads lazily
    vendorChunk: true, // separate chunk for engine packages
  },

  // Target environments
  targets: {
    web: true,
    pwa: true,
    mobile: false,
  },
});

Web Deployment

Static Hosting (Any CDN)

After pnpm build, the dist/ folder is a self-contained static site:

bash
# Upload to any static host:
# - Cloudflare Pages
# - Vercel
# - Netlify
# - GitHub Pages
# - AWS S3 + CloudFront

# Example: Cloudflare Pages
npx wrangler pages deploy dist --project-name my-game

Required Server Headers

For WebGPU and SharedArrayBuffer to work, the server must send these headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

In wrangler.toml (Cloudflare Workers/Pages):

toml
[[headers]]
for = "/*"
  [headers.values]
  Cross-Origin-Opener-Policy = "same-origin"
  Cross-Origin-Embedder-Policy = "require-corp"

In Nginx:

nginx
add_header Cross-Origin-Opener-Policy "same-origin";
add_header Cross-Origin-Embedder-Policy "require-corp";

Cloudflare Workers (Zero-Cold-Start)

typescript
// worker/index.ts
import { getAssetFromKV } from '@cloudflare/kv-asset-handler';

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext) {
    return getAssetFromKV(
      { request, waitUntil: ctx.waitUntil.bind(ctx) },
      {
        ASSET_NAMESPACE: env.__STATIC_CONTENT,
        mapRequestToAsset: (req) => {
          const url = new URL(req.url);
          // SPA fallback: return index.html for unknown routes
          if (!url.pathname.includes('.')) {
            return new Request(`${url.origin}/index.html`, req);
          }
          return req;
        },
      }
    );
  },
};

Progressive Web App (PWA)

Enable offline play and "Add to Home Screen":

typescript
// In your build config
export default defineGameBuildConfig({
  targets: {
    pwa: {
      name: 'My Awesome Game',
      shortName: 'MyGame',
      description: 'An epic adventure game',
      themeColor: '#1a1a2e',
      backgroundColor: '#0a0a14',
      icons: [
        { src: 'icons/icon-192.png', sizes: '192x192' },
        { src: 'icons/icon-512.png', sizes: '512x512' },
        { src: 'icons/icon-maskable.png', sizes: '512x512', purpose: 'maskable' },
      ],
      display: 'fullscreen',
      orientation: 'landscape',
      // Precache all game assets for offline play
      precacheAssets: true,
      cacheStrategy: {
        images: 'cache-first',
        audio: 'cache-first',
        models: 'cache-first',
        api: 'network-first',
      },
    },
  },
});

Mobile (Capacitor)

Wrap your web game as a native iOS/Android app using Capacitor:

bash
# Install Capacitor
pnpm add @capacitor/core @capacitor/cli @capacitor/ios @capacitor/android

# Initialize
npx cap init "My Game" com.example.mygame --web-dir dist

# Add platforms
npx cap add ios
npx cap add android

# Build web, then sync to native
pnpm build && npx cap sync

Capacitor Config

typescript
// capacitor.config.ts
import type { CapacitorConfig } from '@capacitor/cli';

export default {
  appId: 'com.example.mygame',
  appName: 'My Game',
  webDir: 'dist',
  server: {
    androidScheme: 'https',
    iosScheme: 'https',
    // Required headers for WebGPU
    headers: {
      'Cross-Origin-Opener-Policy': 'same-origin',
      'Cross-Origin-Embedder-Policy': 'require-corp',
    },
  },
  plugins: {
    SplashScreen: {
      launchShowDuration: 2000,
      backgroundColor: '#0a0a14',
    },
  },
} satisfies CapacitorConfig;
bash
# Open in Xcode / Android Studio
npx cap open ios
npx cap open android

WebGPU on Mobile

WebGPU availability on mobile as of 2026:

  • iOS 18+: Available via Metal backend
  • Android: Available in Chrome 121+ on supported devices (Vulkan required)

For devices without WebGPU, show a clear error:

typescript
const device = await createDevice({ preferredBackend: 'auto' });
if (!device) {
  document.body.innerHTML = `
    <div style="text-align:center;padding:2rem">
      <h2>WebGPU not supported</h2>
      <p>Please update your browser or device to play.</p>
      <a href="https://caniuse.com/webgpu">Check browser support</a>
    </div>
  `;
  return;
}

Publishing to itch.io

  1. Build your game: pnpm build
  2. Zip the dist/ folder: zip -r mygame.zip dist/
  3. On itch.io:
    • Create a new project → set Kind to "HTML"
    • Upload mygame.zip
    • Check **"This file will be played in the browser"`
    • Set viewport dimensions
    • Add the required COEP/COOP headers via itch.io's "Shared headers" option (required for WebGPU)
  4. Embed the game with the itch.io widget

itch.io COEP/COOP Headers

In your itch.io game page settings → "Embed options" → "SharedArrayBuffer support: Enabled". This sets the required headers automatically.

Environment Variables

bash
# .env.production
VITE_GAME_VERSION=1.0.0
VITE_API_URL=https://api.mygame.com
VITE_ANALYTICS_KEY=GA-XXXXX
VITE_SENTRY_DSN=https://[email protected]/xxx
VITE_DEBUG=false
typescript
// Use in code
const version = import.meta.env.VITE_GAME_VERSION;
const isDev = import.meta.env.DEV;

Analytics & Error Tracking

typescript
import {
  createTelemetry,
  createConsoleTransport,
  createHttpTransport,
} from '@web-engine-dev/telemetry';

const telemetry = createTelemetry({
  // Use an HTTP transport for production; swap in createConsoleTransport() for dev
  transports: [
    createHttpTransport('https://analytics.example.com/events', {
      headers: { Authorization: `Bearer ${import.meta.env.VITE_ANALYTICS_KEY}` },
    }),
  ],
  autoTrackSessions: true, // emits session_start / session_end automatically
});

await telemetry.initialize();

// Hook into game events manually:
// telemetry.trackProgression('level_complete', { level: 1, time: 42.5 });
// telemetry.track('purchase', { item: 'power-up', price: 0.99 });

// For error reporting, wire to an error boundary or engine diagnostics:
// telemetry.trackError({ message: err.message, stack: err.stack });
// - or - Sentry.captureException(err) if you use Sentry directly

Deployment Checklist

Before publishing:

  • [ ] pnpm build succeeds with no warnings
  • [ ] pnpm test passes (or pnpm test:affected)
  • [ ] COEP/COOP headers configured on your host
  • [ ] WebGPU-unavailable error page tested in a non-WebGPU browser
  • [ ] All API keys / secrets in environment variables (not source code)
  • [ ] Analytics and error tracking enabled
  • [ ] Asset compression enabled (bc7, draco, opus)
  • [ ] Cache headers configured (long-lived for hashed assets)
  • [ ] Service Worker registered (if PWA)
  • [ ] Game tested on target devices (Chrome, Edge, Safari iOS 18)
  • [ ] Ads / monetization SDK integrated (if applicable)
  • [ ] Privacy policy linked (required for app stores)

Next Steps

Proprietary software. All rights reserved.