Research: Remotion 4.x + Next.js 16 Server-Side Import Chain — Webpack Externals and Dynamic Import Patterns
Research: Remotion 4.x + Next.js 16 Server-Side Import Chain — Webpack Externals and Dynamic Import Patterns
Date: 2026-03-19 Issue: MOKA-309 Unblocks: MOKA-308 (Narrativ production deployment) Author: Deep Research Agent
Executive Summary
The Narrativ build (next build --webpack) passes locally on the VPS (Linux x64). The reported failure occurs on Vercel during production deployment. Root cause analysis reveals three interacting issues:
- Vercel may default to Turbopack (Next.js 16 default bundler), ignoring the
--webpackflag inpackage.jsonscripts @remotion/compositor-*native binaries are optional dependencies — pnpm may not install them on Vercel if platform differs or optional deps are skipped- The
@remotion/rendererand@remotion/bundlerpackages pull in server-only dependencies that webpack attempts to bundle into client/edge contexts
Current Configuration Analysis
What’s Working (next.config.ts)
serverExternalPackages: [
"@remotion/compositor-darwin-arm64",
"@remotion/compositor-darwin-x64",
"@remotion/compositor-linux-arm64-gnu",
"@remotion/compositor-linux-arm64-musl",
"@remotion/compositor-linux-x64-gnu",
"@remotion/compositor-linux-x64-musl",
"@remotion/compositor-win32-x64-msvc",
"@remotion/bundler",
"@remotion/renderer",
"@remotion/media-parser",
"tsconfig-paths-webpack-plugin",
"enhanced-resolve",
"esbuild",
],
webpack: (config) => {
config.externals = [
...(Array.isArray(config.externals) ? config.externals : []),
"esbuild",
"enhanced-resolve",
];
return config;
},
Verdict: This config is correct for webpack mode. The serverExternalPackages properly excludes all compositor binaries and server-only packages. Local build passes cleanly.
What’s Likely Failing on Vercel
| Issue | Cause | Evidence |
|---|---|---|
| Turbopack default | Next.js 16 uses Turbopack by default; next build in package.json uses --webpack but Vercel may override | Next.js 16 migration guide states Turbopack is default |
| Missing compositor binaries | @remotion/compositor-linux-x64-gnu is an optional dep; not installed locally (confirmed), may fail to resolve on Vercel | ls node_modules/@remotion/ shows no compositor packages |
| tsconfig-paths-webpack-plugin bundling | Listed in serverExternalPackages but may still be imported transitively by @remotion/bundler | Known issue in Remotion docs |
| Client bundle contamination | @remotion/renderer is imported somewhere accessible to client webpack pass | Import chain audit needed |
Recommended Fixes (Ordered by Priority)
Fix 1: Ensure Webpack Mode on Vercel (CRITICAL)
Create vercel.json to explicitly set the build command:
{
"buildCommand": "next build --webpack",
"framework": "nextjs"
}
Why: Vercel auto-detects Next.js and may run next build without --webpack. Since Next.js 16 defaults to Turbopack, and the project has a webpack config in next.config.ts, Turbopack will reject the build entirely.
Fix 2: Separate Client and Server Remotion Usage (HIGH)
The project currently imports @remotion/renderer and @remotion/bundler as top-level dependencies. These are server-only packages that should NEVER appear in any file that could be imported by a client component or page.
Current risk: If any import chain from a page or layout imports these packages, webpack will try to bundle their native dependencies into the client bundle.
Action: Audit all imports of @remotion/renderer and @remotion/bundler:
grep -r "@remotion/renderer\|@remotion/bundler" src/ --include="*.ts" --include="*.tsx"
These should ONLY appear in:
src/app/api/routes (server-only by definition)src/lib/server/filessrc/trigger/tasks/(background jobs)
If found in any other location, wrap with dynamic import:
// BAD: Top-level import in a file that might be reached from client
import { bundle } from "@remotion/bundler";
// GOOD: Dynamic import in API route or server action
const { bundle } = await import("@remotion/bundler");
Fix 3: Add Missing Externals to Webpack Config (HIGH)
Expand the webpack externals to cover all Remotion server packages and their transitive deps:
webpack: (config, { isServer }) => {
if (isServer) {
config.externals = [
...(Array.isArray(config.externals) ? config.externals : []),
"esbuild",
"enhanced-resolve",
"@remotion/bundler",
"@remotion/renderer",
"@remotion/compositor-linux-x64-gnu",
"@remotion/compositor-linux-x64-musl",
"@remotion/compositor-linux-arm64-gnu",
"@remotion/compositor-linux-arm64-musl",
"@remotion/compositor-darwin-arm64",
"@remotion/compositor-darwin-x64",
"@remotion/compositor-win32-x64-msvc",
"@remotion/media-parser",
"tsconfig-paths-webpack-plugin",
];
}
return config;
},
Key change: Use isServer guard so externals only apply to server-side webpack compilation. Client compilation should never encounter these imports at all (if Fix 2 is applied).
Fix 4: Handle Missing Optional Dependencies Gracefully (MEDIUM)
The compositor packages are optionalDependencies of @remotion/renderer. On Vercel (Linux x64), only @remotion/compositor-linux-x64-gnu would be installed — but since they’re optional, pnpm might skip them entirely.
Add to .npmrc or package.json:
# .npmrc
optional=true
Or add the linux compositor as a direct dependency:
{
"dependencies": {
"@remotion/compositor-linux-x64-gnu": "4.0.423"
}
}
Alternatively, if rendering is NOT done on Vercel (it’s done via Trigger.dev workers), then @remotion/renderer should be a devDependency and excluded from the Vercel build entirely.
Fix 5: Consider Removing @remotion/renderer from Vercel Build (RECOMMENDED)
If video rendering happens via Trigger.dev (background jobs on separate infrastructure), then Vercel only needs the Player (client-side preview). The renderer, bundler, and compositor packages are unnecessary.
Strategy:
- Move
@remotion/renderer,@remotion/bundler, and@remotion/clitodevDependencies - Keep only
@remotion/player,remotion, and composition packages independencies - Remove
serverExternalPackagesfor compositor (no longer needed) - API routes that trigger renders should call Trigger.dev, not import renderer directly
{
"dependencies": {
"remotion": "4.0.423",
"@remotion/player": "4.0.423",
"@remotion/animation-utils": "4.0.423",
"@remotion/lottie": "4.0.423",
"@remotion/noise": "4.0.423",
"@remotion/paths": "4.0.423",
"@remotion/shapes": "4.0.423",
"@remotion/three": "4.0.423",
"@remotion/transitions": "4.0.423",
"@remotion/motion-blur": "4.0.423"
},
"devDependencies": {
"@remotion/renderer": "4.0.423",
"@remotion/bundler": "4.0.423",
"@remotion/cli": "4.0.423"
}
}
This is the cleanest solution — it eliminates the entire class of server-side import problems.
tsconfig-paths-webpack-plugin Warning
Remotion docs specifically warn that TypeScript aliases can cause import {} from "remotion" to map to the local src/remotion/ folder instead of the npm package. This is a known issue.
Check: Verify tsconfig.json paths don’t shadow the remotion package:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
If src/remotion/ exists (it does in Narrativ), imports like from "remotion" might resolve incorrectly with certain webpack plugins. The current @/* alias is safe, but if tsconfig-paths-webpack-plugin is ever added to the Next.js webpack config, this could break.
Next.js 16 Specific Considerations
| Feature | Impact |
|---|---|
| Turbopack default | Must use --webpack flag since project has webpack config |
serverExternalPackages | Replaces deprecated serverComponentsExternalPackages |
| React 19 | Remotion 4.0.423 is compatible with React 19 |
| Middleware → Proxy | Deprecation warning shown but not blocking |
use client | Only video-canvas.tsx uses it — correct pattern |
Implementation Checklist
- Create
vercel.jsonwith explicit--webpackbuild command - Audit
@remotion/rendererand@remotion/bundlerimport chains - Move renderer/bundler/cli to
devDependenciesif rendering is via Trigger.dev - Add
isServerguard to webpack externals config - Test build on Vercel after changes
- If render API routes exist on Vercel, add
@remotion/compositor-linux-x64-gnuas direct dep