Browser memory is more limited than native apps. Models larger than 2GB may cause tab crashes on
devices with limited RAM. Start with smaller models and test on target devices.
For browser use, Q4_0 and Q4_K_M offer the best balance of quality and memory efficiency. Start
with smaller quantizations and only increase if output quality is insufficient.
Models over ~200MB can crash the browser tab, especially on memory-constrained devices. Mitigations:
// Check available memory before downloadingif ('deviceMemory' in navigator) { const memoryGB = (navigator as any).deviceMemory const modelSizeMB = 250 if (memoryGB < 4 && modelSizeMB > 200) { console.warn('Low memory device — large model downloads may fail.') }}// Monitor for download stalls (no progress for 30s may indicate trouble)let lastProgress = 0let lastTime = Date.now()EventBus.shared.on('model.downloadProgress', (evt) => { const progress = evt.progress ?? 0 if (progress > lastProgress) { lastProgress = progress lastTime = Date.now() } else if (Date.now() - lastTime > 30000) { console.warn('Download appears stalled. Tab may be running low on memory.') }})
If the browser tab crashes during a model download, the partial download is stored in OPFS. On the
next attempt, ModelManager.downloadModel() will resume from where it left off. Recommend
starting with smaller models (LFM2 350M at ~250MB) before attempting larger ones.
These platforms run your app inside an iframe, which has important implications:
Limitation
Impact
Workaround
No SharedArrayBuffer
WASM runs single-threaded (slower)
Access app directly, not via iframe preview
Memory constraints
Large models (>200MB) may crash
Use smaller models (350M-500M params, Q4_0)
COOP header conflict
same-origin breaks iframe embedding
SDK falls back gracefully; no action needed
Preview URL differs
CORS/COEP may behave differently
Test with the published URL, not the preview
Do not use COEP: require-corp in hosted IDE environments. It will block Vite’s internal
/@fs/ module serving and cause “non-JavaScript MIME type” errors for worker scripts and WASM
glue files. Always use COEP: credentialless.
When using a custom Express/Node server with SPA catch-all routing, static asset routes must come before the catch-all. Otherwise, .wasm, .js, and worker files get served as index.html:
// WRONG ORDER — catch-all swallows WASM requests:app.get('*', (req, res) => res.sendFile('index.html'))app.use(express.static('dist')) // Never reached for .wasm files// CORRECT ORDER — static files served first:app.use( express.static('dist', { setHeaders: (res, path) => { if (path.endsWith('.wasm')) { res.setHeader('Content-Type', 'application/wasm') } }, }))app.get('*', (req, res) => { if (!req.path.match(/\.(js|css|wasm|json|png|svg|woff2?)$/)) { res.sendFile('index.html', { root: 'dist' }) } else { res.status(404).end() }})
if ('deviceMemory' in navigator) { const memory = (navigator as any).deviceMemory // GB if (memory < 4) { console.warn('Low memory device. Use smaller models.') }}
Provide clear error messages for camera permissions (important for VLM):
try { const camera = new VideoCapture({ facingMode: 'environment' }) await camera.start()} catch (err) { const msg = (err as Error).message if (msg.includes('NotAllowed') || msg.includes('Permission')) { alert('Camera permission denied. Check System Settings → Privacy & Security → Camera.') } else if (msg.includes('NotFound')) { alert('No camera found on this device.') } else if (msg.includes('NotReadable')) { alert('Camera is in use by another application.') }}