Realtime Mockup
useRealtimeMockup.Interactive Demo
Try the interactive playground below - draw on the canvas and watch the product mockup update in real-time via WebSocket:
Overview
The useRealtimeMockup hook connects to a WebSocket server for instant mockup generation as users make changes. Perfect for real-time product configurators and interactive design tools.
✨ Features
- • WebSocket connection with auto-reconnect
- • Real-time mockup generation (sub-second updates)
- • WebP export format (~10x smaller than PNG) for fast uploads
- • Event-driven auto-export with smart debouncing
- • Multi-placement support (image + color placements)
- • Fit mode with margins and alignment for precise artwork positioning
- • Worker-based export (non-blocking UI)
- • Session management and debug logging
- • Automatic cleanup on unmount
SnowconeCanvas from @snowcone-app/canvas with exportImageFormat="webp" for ~10x smaller uploads compared to PNG. You can use any canvas library or drawing tool that can export image blobs. The hook only requires a blob to send via the sendCanvasBlob method.Direct WebSocket protocol
The useRealtimeMockup hook speaks this for you. Driving the socket directly (a non-React or non-JS client) means three things: a JSON config message, one binary frame per placement, then a sequence of result messages.
// 1. Configure the session. Server replies with the placements it expects.
{ "type": "config", "config": {
"productId": "BEEB77",
"mockupIds": ["FV1qjO"],
"variantId": "Pv1sLC",
"shop": "dqMwU8Jct3",
"width": 1000
} }
// ← { "type": "config_received", "requiredPlacements": ["Front"] }
//
// Optional: "ar" (aspect ratio, default "16:9"), "placementSettings".
// On the token path the billed shop comes from the session token, not "shop".Artwork is sent as raw binary, not JSON — a UTF-8 header line naming the placement and version, then the image bytes. A JSON header is rejected with Invalid placement.
// 2. Send each placement's artwork as a BINARY frame (NOT JSON):
// a UTF-8 header "<placement>\n<version>\n" then the raw image bytes.
const header = new TextEncoder().encode(`${placement}\n${version}\n`);
const bytes = new Uint8Array(await blob.arrayBuffer());
const frame = new Uint8Array(header.length + bytes.length);
frame.set(header, 0);
frame.set(bytes, header.length);
ws.send(frame); // sending JSON here yields: Invalid placement "{…}"Message lifecycle once connected:
connected { sessionId }
→ config_received { requiredPlacements }
→ (client sends one binary blob per required placement)
→ have_all_blobs
→ rendering_started
→ mockup_rendered { mockupId, imageUrl, placement, renderMs } # one per mockup
→ all_mockups_rendered { mockups: [ … ] }imageUrl is an ephemeral, session-scoped render — download or copy it to your own storage if it needs to outlive the connection. Required config fields are productId, mockupIds, variantId, and width; mockup_rendered arrives once per mockup, then all_mockups_rendered closes the batch.useRealtimeMockup Options
wsUrlstringgetToken() => Promise<{ token: string; expiresAt: number }>1008 — see Authorizing the session.Hook Return Values
isConnectedbooleansessionIdstring | nullisConfiguredbooleanmockupResultsMockupResult[]imageUrl and mockupId.statusstringsendConfig(config: WebSocketConfig) => voidsendCanvasBlob(placement: string, blob: Blob, version: number, updateMs: number) => voidInstallation
The hook and components ship in @snowcone-app/ui. Peers: react, react-dom, zod.
@import '@snowcone-app/ui/styles/globals.css' (brings in Tailwind, the theme tokens, and an @source for the package) plus an @source for your own code. In Next.js, also add transpilePackages: ['@snowcone-app/ui']. Add className="dark" on an ancestor for dark mode.SnowconeCanvas component used in the example is from @snowcone-app/canvas. You can use any canvas library that exports blobs - the key is implementing the onExport callback.
