Deployment Guide

How to deploy your Verani application to Cloudflare.

Prerequisites

  • Cloudflare account (free tier works)
  • Wrangler CLI installed: npm install -g wrangler
  • Logged in to Wrangler: wrangler login

Project Structure

Your project should look like this:

my-app/
├── src/
│   ├── index.ts          # Actor handler export
│   └── rooms/
│       └── chat.ts       # Room definitions
├── wrangler.toml         # Cloudflare configuration
├── package.json
└── tsconfig.json

Local Development

Test locally before deploying:

npm run dev
# or
wrangler dev

This starts a local server at http://localhost:8787.

Test WebSocket connection:

const ws = new WebSocket("ws://localhost:8787/ws?userId=alice");

ws.onopen = () => {
  console.log("Connected!");
  ws.send(JSON.stringify({ type: "ping" }));
};

ws.onmessage = (e) => {
  console.log("Received:", e.data);
};

Deploy to Production

Deploy your Worker:

wrangler deploy

Your app will be available at:

https://my-verani-app.your-subdomain.workers.dev

WebSocket endpoint:

wss://my-verani-app.your-subdomain.workers.dev/ws

Custom Domain (Optional)

Add a Custom Domain

  1. Go to Cloudflare Dashboard → Workers & Pages
  2. Select your Worker
  3. Go to Settings → Triggers
  4. Add a custom domain (e.g., chat.example.com)

Update Client

const client = new VeraniClient(
  "wss://chat.example.com/ws?userId=alice"
);

Common Deployment Issues

Issue: WebSocket connection fails

Solutions:

  1. Check you're using wss:// for production (not ws://)
  2. Verify the /ws path is correct
  3. Check browser console for errors
  4. Run wrangler tail to see server-side errors

Issue: Sessions not restoring after hibernation

Solution: Make sure you're using Verani's storeAttachment():

// This is automatic in Verani
storeAttachment(ws, metadata);

Issue: CORS errors in browser

Solution: Add CORS headers in Worker:

export default {
  async fetch(request, env) {
    // Handle CORS preflight
    if (request.method === "OPTIONS") {
      return new Response(null, {
        headers: {
          "Access-Control-Allow-Origin": "*",
          "Access-Control-Allow-Methods": "GET, POST",
          "Access-Control-Allow-Headers": "Content-Type",
        },
      });
    }

    // Your handler
    return handler.fetch(request, env);
  }
};

Persistence in Production

When using state persistence:

  • Test persistence: Verify state survives hibernation and server restarts
  • Handle errors: Implement onPersistError to handle persistence failures gracefully
  • Monitor storage: Check Durable Object storage usage and costs
  • Optimize keys: Only persist what you need - each key adds storage overhead
const room = defineRoom({
  state: {
    // Only persist essential state
    messageCount: 0,
    settings: { maxUsers: 100 }
  },
  persistedKeys: ["messageCount", "settings"], // Minimal set
  
  persistOptions: {
    shallow: true, // Faster than deep proxying
    throwOnError: false // Don't crash on persistence failures
  },
  
  onPersistError(key, error) {
    // Log to monitoring service
    console.error(`[Persistence] Failed to persist ${key}:`, error);
    // Maybe notify admins or use fallback storage
  }
});

See: Persistence Concepts for full documentation.

Security Checklist

Before going to production:

  • Implement authentication (JWT tokens) - See Security Guide - Authentication
  • Validate all client input
  • Rate limit messages per user
  • Sanitize user-generated content
  • Use HTTPS/WSS only (never HTTP/WS)
  • Don't expose error details to clients
  • Log security events
  • Set up monitoring and alerts
  • Verify Origin header to prevent CSWSH attacks
  • Use environment variables for secrets (never commit secrets)
  • Test state persistence across hibernation cycles
  • Implement error handling for persistence failures

📖 Read the complete Security Guide for implementation details.

Next Steps

Support