If you're deploying a Next.js app with Playwright and Chromium to Vercel, you've probably hit this error:
/tmp/chromium: error while loading shared libraries: libnspr4.so: cannot open shared object file: No such file or directory
It works perfectly on your local machine but fails on Vercel. This is one of the most frustrating deployment issues developers face when working with headless browsers in serverless environments.
After analyzing 50+ GitHub issues and testing multiple solutions, I discovered the root cause and the complete fix. Here's everything you need to know.
Why It Works Locally But Fails on Vercel
The fundamental difference between your local environment and Vercel's serverless environment causes this issue.
On your local machine:
- System libraries like
libnspr4.soandlibnss3.soare pre-installed - You have full file system access
- Complete Node.js environment with all dependencies
On Vercel serverless:
- Minimal Linux container with no system libraries
- Read-only file system except for the
/tmpdirectory - Environment variables must exist before modules load
The critical issue: The sparticuz/chromium package checks for AWS_LAMBDA_JS_RUNTIME when the module imports, but if you set it in your code, it happens after the module is already loaded. This timing mismatch breaks everything.
The Five Problems and Their Solutions
Problem 1: Wrong Package Choice
Many developers try to use the full Playwright package, which doesn't work on Vercel.
Don't use:
"playwright": "^1.39.0"
This package exceeds Vercel's 50MB limit and tries to download the browser at runtime, which fails in serverless environments.
Use instead:
{
"dependencies": {
"playwright-core": "1.39.0",
"@sparticuz/chromium": "^131.0.0"
}
}
Why this works: playwright-core is only about 5MB, and the sparticuz chromium package is about 40MB. Together they stay under Vercel's 50MB limit, and the chromium package provides a pre-bundled Chromium optimized for serverless environments.
Problem 2: Environment Variable Timing
This is where most developers get stuck. The timing of when environment variables are set matters.
The problem:
When you import the package, it immediately checks for AWS_LAMBDA_JS_RUNTIME. But if you set it in your code like this:
import chromiumPack from "@sparticuz/chromium";
process.env.AWS_LAMBDA_JS_RUNTIME = "nodejs22.x";
The module has already loaded and checked for the variable. It's too late.
The solution:
Set the environment variable in the Vercel Dashboard, not just in your code.
Here's how:
- Go to Vercel Dashboard
- Select your project
- Go to Settings → Environment Variables
- Add a new variable:
AWS_LAMBDA_JS_RUNTIMEwith valuenodejs22.x - Apply to Production, Preview, and Development environments
- Redeploy your application
Why the Dashboard matters: Environment variables set in the Vercel Dashboard are available before any modules load, which is exactly when the chromium package needs them.
Problem 3: Libraries Extracted But Not Found (The Critical Fix)
This was the breakthrough that solved the issue. Even when libraries are extracted correctly, Chromium can't find them.
The problem:
- Libraries are extracted to
/tmp/libnspr4.soand/tmp/libnss3.so - Chromium executable is in
/tmp/chromium - But Chromium can't find the libraries because the system doesn't know where to look
The solution:
Set LD_LIBRARY_PATH to the executable directory before launching the browser.
Here's the code:
const executablePath = await chromiumPack.executablePath();
const execDir = path.dirname(executablePath);
// CRITICAL: Set LD_LIBRARY_PATH so Chromium can find libraries
process.env.LD_LIBRARY_PATH = execDir;
Why this works: LD_LIBRARY_PATH tells the Linux system loader where to find shared libraries. By setting it to the same directory where the Chromium executable and libraries are located, Chromium can successfully load the required libraries.
This was the missing piece that most solutions don't mention. Without this, even with the correct environment variable, Chromium will fail to find the libraries.
Problem 4: Old Package Version
Using an outdated version of the chromium package can cause compatibility issues with Node.js 22.x.
Don't use:
"@sparticuz/chromium": "119.0.2"
Use:
"@sparticuz/chromium": "^131.0.0"
Version 131.0.0 has better support for Node.js 22.x and includes fixes for common serverless deployment issues.
Problem 5: Browser Freezing
Some developers report that the browser freezes after creating a new page, even when everything else is configured correctly.
The problem: Browser freezes after "Creating new page" message appears.
The solution:
Disable graphics mode before launching the browser:
if (typeof chromiumPack.setGraphicsMode === 'function') {
chromiumPack.setGraphicsMode(false);
}
Why: Serverless environments don't have GPU support. Disabling graphics mode prevents the browser from trying to use GPU features that aren't available, which causes freezing.
Complete Working Solution
Here's the complete implementation that works on Vercel:
1. Package.json Configuration
{
"dependencies": {
"@sparticuz/chromium": "^131.0.0",
"playwright-core": "1.39.0"
},
"engines": {
"node": "22.x"
}
}
2. Next.js Configuration
In your next.config.ts file:
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
serverExternalPackages: [
'playwright-core',
'@sparticuz/chromium',
],
};
export default nextConfig;
This prevents Next.js from bundling these packages, which is essential for them to work correctly in the serverless environment.
3. Browser Launch Code
Create a file lib/browser.ts with this code:
import { chromium, Browser } from "playwright-core";
import chromiumPack from "@sparticuz/chromium";
import * as path from "path";
let browserInstance: Browser | null = null;
export async function getBrowser(): Promise<Browser> {
if (browserInstance) return browserInstance;
const isVercel = !!(process.env.VERCEL || process.env.VERCEL_ENV);
if (isVercel) {
// Set runtime (fallback if not in Dashboard)
if (!process.env.AWS_LAMBDA_JS_RUNTIME) {
process.env.AWS_LAMBDA_JS_RUNTIME = "nodejs22.x";
}
// Set graphics mode to prevent freezing
if (typeof chromiumPack.setGraphicsMode === 'function') {
chromiumPack.setGraphicsMode(false);
}
// Get executable and set library path
const executablePath = await chromiumPack.executablePath();
const execDir = path.dirname(executablePath);
// CRITICAL: Set LD_LIBRARY_PATH
process.env.LD_LIBRARY_PATH = execDir;
browserInstance = await chromium.launch({
args: chromiumPack.args,
executablePath,
headless: true,
});
} else {
// Local development
browserInstance = await chromium.launch({
args: ["--no-sandbox"],
headless: true,
});
}
return browserInstance;
}
4. API Route Configuration
In your API route file (e.g., app/api/screenshot/route.ts):
import { NextRequest, NextResponse } from "next/server";
import { getBrowser } from "@/lib/browser";
export const runtime = "nodejs";
export const maxDuration = 300;
export const dynamic = "force-dynamic";
export async function POST(request: NextRequest) {
const browser = await getBrowser();
// Your screenshot code here
}
The runtime = "nodejs" ensures the function runs in the Node.js runtime, and maxDuration = 300 gives you 5 minutes for screenshot operations (requires Vercel Pro plan).
5. Vercel Dashboard Settings
Before deploying, configure these settings in your Vercel Dashboard:
Required settings:
-
Environment Variable: Add
AWS_LAMBDA_JS_RUNTIMEwith valuenodejs22.x - Disable Fluid Compute: Go to Settings → Functions → Fluid Compute → Turn OFF
- Pro Plan: Required for 300-second timeouts (Hobby plan only allows 10 seconds)
The Three Critical Fixes
You need all three of these fixes for Chromium to work on Vercel:
-
Correct packages: Use
playwright-coreand sparticuz chromium version 131.0.0 or higher -
Environment variable: Set
AWS_LAMBDA_JS_RUNTIME=nodejs22.xin Vercel Dashboard -
Library path: Set
LD_LIBRARY_PATHto the executable directory in your code
Missing any one of these will cause the deployment to fail. They work together:
- The environment variable tells the package which runtime to use
- The library path tells Chromium where to find the extracted libraries
- The correct packages ensure everything fits within Vercel's limits
Quick Troubleshooting Checklist
If you're still experiencing issues, check each of these:
- [ ]
AWS_LAMBDA_JS_RUNTIME=nodejs22.xis set in Vercel Dashboard (not just in code) - [ ]
LD_LIBRARY_PATHis set in code before browser launch - [ ] Using
playwright-core(not the full playwright package) - [ ] Using sparticuz chromium version 131.0.0 or higher
- [ ]
setGraphicsMode(false)is called before launching the browser - [ ] Fluid Compute is disabled in Vercel settings
- [ ] You're on Vercel Pro plan for extended timeouts
- [ ]
serverExternalPackagesis configured innext.config.ts
Why This Solution Works
This solution addresses all the fundamental differences between local and serverless environments:
-
Package choice:
playwright-coreand the chromium package are designed for serverless and stay within size limits - Environment variable: Setting it in the Dashboard ensures it's available when modules load
-
Library path:
LD_LIBRARY_PATHtells the system where to find shared libraries in the minimal serverless environment
The critical insight: Both the environment variable (for runtime detection) and the library path (for finding libraries) are required. Most solutions only mention one or the other, but you need both.
Conclusion
Deploying Chromium on Vercel requires three essential components:
- The right packages (
playwright-coreand the chromium package) - Environment variable configured in Vercel Dashboard
- Library path set in your code
Once you have all three configured correctly, your screenshot application will work reliably on Vercel's serverless infrastructure. The key is understanding that serverless environments are fundamentally different from local development, and each difference requires a specific solution.
This solution was discovered after analyzing 50+ GitHub issues and testing multiple approaches. The breakthrough was realizing that LD_LIBRARY_PATH must be set to the executable directory, which most documentation doesn't mention.
Resources
If you found this helpful, feel free to share your experience or ask questions in the comments below!
Top comments (0)