VS

Troubleshooting

Each entry has the symptom, the cause and the fix. Search this page for the text in your console.

Vite build fails with "UMD and IIFE output formats are not supported for code-splitting builds"

Cause: the engine's workers load their code in chunks, and Vite's default worker format cannot emit chunks.

Fix: set the worker format to ES modules in vite.config.ts.

import { defineConfig } from "vite";

export default defineConfig({
  worker: { format: "es" },
});

Vite dev server fails to load the export or transcode worker

The console shows Transcode worker error or does not provide an export named 'default', and the export silently runs on the main thread.

Cause: the dev server serves a worker from node_modules and resolves one of its dependencies to a CommonJS file that was not pre-bundled. Version 0.1.0 on Vite 5.4.21 does not show this; an earlier build did.

Fix: name the dependency from the error message in optimizeDeps.

import { defineConfig } from "vite";

export default defineConfig({
  worker: { format: "es" },
  optimizeDeps: { include: ["pixi.js"] },
});

Fonts return 404 in Next.js

The console shows a 404 for /fonts/Inter-Regular.woff2 and [FontRegistry] Failed to load font "Inter".

Cause: text tracks load fonts from /fonts/, and nothing puts them there.

Fix: copy the fonts into the public folder once.

cp -r node_modules/@vidstudio/engine/assets/fonts public/fonts

Or serve them elsewhere and pass options={{ assets: { fonts: "https://static.example.com/fonts/" } }}, ending in a slash.

Models return 404

The console shows a 404 for /models/onnx-community/whisper-base.en/config.json when a transcription starts.

Cause: the speech models are not in the package; they are fetched once by a script.

Fix: run the script, or turn captions off.

npx vidstudio-fetch-models public/models

Or pass options={{ assets: { models: "https://static.example.com/models/" } }}, or pass features={{ captions: false }} so no model is ever requested.

The mark is drawn on a licensed domain

The preview and the exports carry the "vidstudio.app" mark although you passed a key.

Cause: one of five things, listed in the table.

Cause How to tell Fix
Old browser Chrome before 137, Safari before 17, Firefox before 129 cannot check Ed25519 Use a newer browser
Plain http:// on a network address window.isSecureContext is false, so WebCrypto is missing Serve over HTTPS; only localhost is secure without it
Domain mismatch The key covers example.com and the page is on example.org Ask for a key that lists the page's domain; subdomains and www. are covered already
Name mismatch licence.name differs from the name the key was issued to Pass the exact name you were given
Expired The key's end date has passed Ask for a new key

Add this function to your page and call it with your licence to see which one it is.

import { checkLicence, readLicenceKey } from "@vidstudio/engine";

export async function inspectLicence(licence: { name: string; key: string }): Promise<void> {
  console.log(await readLicenceKey(licence.key));
  console.log(await checkLicence(licence, location.hostname));
}

readLicenceKey returns null when the signature or the format is wrong, otherwise the name, domains and end date. checkLicence returns the state for the host.

Transcription is slow

A minute of speech takes about twice as long as expected.

Cause: without the cross-origin isolation headers the speech runtime runs on one thread.

Fix: send both headers from your server. Then every cross-origin resource the page loads must send Cross-Origin-Resource-Policy: cross-origin too.

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

Without the headers everything still works; only transcription is slower.

An import or export fails with "would exceed its storage quota"

The backend emits a storage-full event and the operation rejects.

Cause: the browser gave this origin less storage than the file needs. A private window and a Playwright context without a persistent profile get a very small quota.

Fix: free space on the device, or use a normal window. Delete finished exports with discardExportFile. In Playwright, use launchPersistentContext.

importFromUrl rejects with import-failed

The address is correct and opens in a tab, but the import fails.

Cause: the file is on another origin and that server does not allow your page to read it.

Fix: have the server send Access-Control-Allow-Origin with your origin, or *. With the isolation headers on your page, it must also send Cross-Origin-Resource-Policy: cross-origin.

Server rendering fails with "navigator is not defined"

Next.js, or another server renderer, throws ReferenceError: navigator is not defined when a page imports @vidstudio/editor.

Cause: the editor's rendering library reads the browser at module load time, and there is no browser on the server.

Fix: load the editor only in the browser, inside an effect.

import { useEffect, useRef } from "react";
import type { MountedEditor, VidstudioEditorProps } from "@vidstudio/editor";

export function ClientEditor({ store, projectId }: Pick<VidstudioEditorProps, "store" | "projectId">) {
  const element = useRef<HTMLDivElement>(null);
  useEffect(() => {
    let editor: MountedEditor | null = null;
    import("@vidstudio/editor").then(({ mountEditor }) => {
      if (element.current) editor = mountEditor(element.current, { store, projectId });
    });
    return () => editor?.unmount();
  }, [store, projectId]);
  return <div ref={element} style={{ height: "100vh" }} />;
}

In Next.js, dynamic(() => import("./Editor"), { ssr: false }) does the same for a component file that imports the package at the top.

Two editors on one page show each other's media

Importing into one editor adds the clip to the other, or deleting in one removes from both.

Cause: both editors use the same namespace, so they share one database and one file folder.

Fix: give each editor its own namespace, in the store and in the options.

import { createStore } from "@vidstudio/editor";

export async function twoStores() {
  const left = await createStore({ namespace: "left" });
  const right = await createStore({ namespace: "right" });
  return {
    left: { store: left, options: { namespace: "left" } },
    right: { store: right, options: { namespace: "right" } },
  };
}

Pass each pair as the store and options props of its editor.

A notice sits over the timeline

A bar in the lower left of the editor reads "This editor is unlicensed for this domain, so its picture and exports carry a watermark."

Cause: the licence state is "invalid" on this host. On localhost there is no notice.

Fix: pass a key that covers the domain. The user can dismiss the bar with its close button; it comes back on the next mount.

Export rejects with missing-media

exportProject rejects with the message "<file>" is missing. Remove its clips or open the project with the file before exporting.

Cause: when the project was loaded, the resolver threw or the download failed for that asset, so it loaded with missing: true.

Fix: load the document again with a resolver that returns the file, or delete the clips that play it. getMediaAssets() lists the assets; the missing ones have missing: true.

A warning about a Canvas 2D fallback in headless Chromium

The console shows PixiJS WebGL init failed followed by using Canvas 2D fallback.

Cause: headless Chromium has no WebGL unless it is launched with a software renderer, so the preview falls back to Canvas 2D.

Fix: nothing is broken; the frames are the same. To silence it, pass options={{ renderer: "canvas2d" }} in automation.