How to Fix: FFMpeg Wasm lib not loading with turbopack dev enabled.

5 min read

FFmpeg.wasm fails under Turbopack dev because the worker and core asset files are resolved differently in Next.js development when –turbopack is enabled, so the browser requests files from paths Turbopack does not currently serve the same way as Webpack. The visible symptom is usually a stalled load, 404s for FFmpeg assets, or a worker initialization error in the browser console.

Understanding the Root Cause

The issue happens at the intersection of Next.js Turbopack, ffmpeg.wasm, and how browser-side workers load dependent files.

ffmpeg.wasm typically needs several runtime assets:

  • a JavaScript loader such as ffmpeg-core.js
  • a WebAssembly binary such as ffmpeg-core.wasm
  • sometimes a worker script

In a standard setup, these files are loaded either from a CDN or from the app’s public directory. Under classic Webpack dev mode, some patterns appear to work because asset resolution is more forgiving or matches the library’s assumptions. Under Turbopack dev, worker-related and dynamically loaded asset paths are stricter, and packages that expect bundler-specific URL rewriting may fail.

In practical terms, the root cause is usually one of these:

  • Relative asset URLs inside ffmpeg.wasm do not resolve correctly when served by Turbopack.
  • The library tries to load worker or wasm files from a location that is not exposed as a static asset.
  • The package is initialized during server rendering or too early in the lifecycle, even though it requires a browser environment.
  • Cross-origin isolation or worker execution constraints block some runtime behavior depending on browser and package version.

That is why the bug often reproduces only when running next dev –turbopack, while production builds or non-Turbopack dev can behave differently.

Step-by-Step Solution

The most reliable fix is to stop relying on package-internal relative asset discovery during development and instead serve FFmpeg core files explicitly from the public directory, then load them with absolute URLs on the client only.

1. Install the required package

npm install @ffmpeg/ffmpeg @ffmpeg/util

2. Copy FFmpeg runtime assets into the public folder

Create a directory like public/ffmpeg and place the runtime files there. Depending on the package version, these are commonly:

  • ffmpeg-core.js
  • ffmpeg-core.wasm
  • ffmpeg-core.worker.js if your version includes it

You can copy them from the installed package during setup or via a small script.

mkdir -p public/ffmpeg

If you want to automate copying, use a script similar to this:

const fs = require('fs');
const path = require('path');

const sourceDir = path.dirname(require.resolve('@ffmpeg/core/package.json'));
const targetDir = path.join(process.cwd(), 'public/ffmpeg');

fs.mkdirSync(targetDir, { recursive: true });

for (const file of ['ffmpeg-core.js', 'ffmpeg-core.wasm', 'ffmpeg-core.worker.js']) {
  const src = path.join(sourceDir, 'dist', 'umd', file);
  if (fs.existsSync(src)) {
    fs.copyFileSync(src, path.join(targetDir, file));
  }
}

Save that as something like scripts/copy-ffmpeg-assets.js and run it after install if needed.

3. Load FFmpeg only on the client

Create a client component and ensure FFmpeg is initialized only in the browser.

'use client';

import { useRef, useState } from 'react';
import { FFmpeg } from '@ffmpeg/ffmpeg';

export default function FfmpegDemo() {
  const ffmpegRef = useRef(null);
  const [loaded, setLoaded] = useState(false);
  const [loading, setLoading] = useState(false);

  const load = async () => {
    if (loaded || loading) return;
    setLoading(true);

    const ffmpeg = new FFmpeg();

    await ffmpeg.load({
      coreURL: '/ffmpeg/ffmpeg-core.js',
      wasmURL: '/ffmpeg/ffmpeg-core.wasm',
      workerURL: '/ffmpeg/ffmpeg-core.worker.js'
    });

    ffmpegRef.current = ffmpeg;
    setLoaded(true);
    setLoading(false);
  };

  return (
    <div>
      <button onClick={load} disabled={loading || loaded}>
        {loaded ? 'FFmpeg Loaded' : loading ? 'Loading...' : 'Load FFmpeg'}
      </button>
    </div>
  );
}

This approach fixes the main Turbopack issue because the browser now fetches files from /public-backed URLs that Next.js serves consistently.

4. Dynamically import the component if needed

If your route is using the App Router and you want to be extra safe, dynamically import the FFmpeg component with SSR disabled.

import dynamic from 'next/dynamic';

const FfmpegDemo = dynamic(() => import('./FfmpegDemo'), {
  ssr: false,
});

export default function Page() {
  return <FfmpegDemo />;
}

5. Verify asset loading in the browser

Open DevTools and confirm these URLs return 200 responses:

If one of them returns 404, Turbopack is not the real problem anymore; the issue is your asset placement or version mismatch.

6. Use a fallback if Turbopack dev is blocking local development

If you need an immediate unblock while waiting for upstream fixes, run regular Next dev without Turbopack for this feature branch.

next dev

This is not the best long-term fix, but it is a practical workaround when the project depends heavily on WebAssembly workers and you need development stability.

Common Edge Cases

  • Version mismatch between @ffmpeg/ffmpeg and core assets: if the package version expects a different runtime file layout, loading will fail even with correct paths.
  • Missing worker file: some examples only provide coreURL and wasmURL, but your installed version may also require workerURL.
  • Server Component usage: importing FFmpeg directly into a Server Component can trigger build or runtime issues because browser APIs are unavailable.
  • Incorrect basePath or assetPrefix: if your Next.js app uses a base path, the asset URLs must include it, such as /myapp/ffmpeg/ffmpeg-core.js.
  • Cross-origin asset hosting: if you host runtime files on a CDN, the CDN must return the correct headers or worker/wasm fetches may fail.
  • Static export assumptions: if your app is exported statically, verify the generated output still includes the FFmpeg assets at the exact paths your code requests.

FAQ

Why does FFmpeg.wasm work with Webpack dev but not with Turbopack dev?

Because Turbopack handles some dynamic asset and worker resolution paths differently during development. Libraries that rely on implicit bundler behavior often break unless you provide explicit public URLs.

Do I need to disable SSR for FFmpeg.wasm?

In most Next.js setups, yes. FFmpeg.wasm is a browser-only dependency and should be loaded from a client component, often with a dynamic import using ssr: false.

Is this a Turbopack bug or an FFmpeg.wasm bug?

It is usually a compatibility gap between how ffmpeg.wasm expects assets to be discovered and how Turbopack dev serves or rewrites them. The safest app-level fix is to serve the runtime files yourself from public and pass explicit URLs.

The key takeaway is simple: when FFmpeg.wasm does not load with next dev –turbopack, treat the wasm and worker files as explicit static assets, load them only on the client, and avoid depending on automatic bundler path inference.

Leave a Reply

Your email address will not be published. Required fields are marked *