
Content creator and developer at UICraft Marketplace, sharing insights and tutorials on modern web development.
Save hours of development time with our premium Next.js templates. Built with Next.js 16, React 19, and Tailwind CSS 4.
Get the latest articles, tutorials, and product updates delivered to your inbox.
You're building authentication in your Next.js application, and everything looks perfect—until you import bcrypt. Suddenly, your Route Handler throws a cryptic error about node-pre-gyp, an HTML file it can't parse, or a missing package.json. Your build fails, your deployment crashes, and you're left wondering why a battle-tested library like bcrypt doesn't work in a modern framework like Next.js.
This is one of the most common and frustrating issues developers encounter when implementing password hashing in Next.js Route Handlers. The good news? Once you understand the root cause, the fix is straightforward. In this comprehensive guide, we'll explore why this Next.js bcrypt crash occurs, how to resolve it immediately, and how to prevent similar issues in the future.
When you import the bcrypt package in a Next.js Route Handler (the route.ts or route.js files in the App Router's app/api directory), you'll often encounter one of several error patterns:
Error Pattern 1: Module Parse Failed
error - ./node_modules/@mapbox/node-pre-gyp/lib/util/nw-pre-gyp/index.html
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type,
currently no loaders are configured to process this file.
> <!doctype html>
| <html>
| <head>
Error Pattern 2: Missing Dependencies
error - ./node_modules/@mapbox/node-pre-gyp/lib/util/s3_setup.js:43:0
Module not found: Can't resolve 'mock-aws-s3'
Error Pattern 3: Package.json Not Found
Error: /path/to/project/.next/server/app/api/package.json does not exist
at exports.find (node_modules/@mapbox/node-pre-gyp/lib/pre-binding.js:14:15)
at eval (node_modules/bcrypt/bcrypt.js:4:31)
These errors are well-documented in the community, with GitHub issue #46493 being one of the most referenced discussions about Route Handlers crashing with bcrypt. The issue has generated extensive community discussion because authentication—the primary use case for bcrypt—is a core feature of most web applications.
The problem becomes even more pronounced when you try to use bcrypt in Middleware (now called Proxy in Next.js 16+). The GitHub discussion #69166 documents this exact scenario, where developers attempting to implement authentication checks in middleware encounter immediate build failures.
Here's a simple Route Handler that will crash:
// app/api/auth/route.ts - This will crash!
import bcrypt from "bcrypt";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const { password } = await request.json();
// This line never executes - the import itself crashes
const hashedPassword = await bcrypt.hash(password, 10);
return NextResponse.json({ hash: hashedPassword });
}To understand why this Next.js bcrypt crash occurs, we need to understand how bcrypt works and how Next.js handles code execution.
The bcrypt npm package is a native Node.js module. Unlike pure JavaScript packages, it includes C++ code that must be compiled for your specific operating system and architecture. This native code provides significant performance benefits—bcrypt's hashing is computationally intensive by design, and native code executes much faster than JavaScript equivalents.
The package uses node-pre-gyp to manage these native bindings. When you install bcrypt, it either downloads pre-built binaries for your system or compiles them locally. This creates a dependency chain that includes:
.node binary files@mapbox/node-pre-gyp package for binary managementNext.js uses Webpack (or Turbopack) to bundle your application code. When it encounters an import statement, the bundler attempts to resolve and include all dependencies in the final bundle. This is where the conflict arises.
Route Handlers in the App Router can run in two different runtimes:
When Webpack processes your Route Handler, it doesn't always know which runtime will execute the code. It attempts to bundle bcrypt as if it might run in any environment, including the browser. This causes:
node-pre-gyp's dynamic requiresMiddleware (or Proxy in Next.js 16+) historically runs exclusively on the Edge Runtime. The Edge Runtime is designed for speed and global distribution, but it explicitly does not support native Node.js modules. There's no file system access, no native compilation, and no node-pre-gyp.
This means any attempt to import bcrypt in middleware will fail, regardless of configuration. As documented in GitHub discussion #69166, this has been a significant pain point for developers implementing authentication.
Important Update: As of Next.js 15.2, Node.js runtime support for Middleware is available experimentally. In Next.js 15.5, it became stable. This means you can now potentially use native modules in middleware, though it requires explicit configuration.
There are several approaches to fix the Next.js Route Handlers bcrypt crash, depending on your requirements and constraints.
The most straightforward fix is to replace bcrypt with bcryptjs. This package is a pure JavaScript implementation of the bcrypt algorithm with an identical API:
# Remove bcrypt and install bcryptjs
npm uninstall bcrypt
npm install bcryptjs
npm install -D @types/bcryptjs # For TypeScript projectsUpdate your imports:
// app/api/auth/route.ts - Works perfectly!
import bcrypt from "bcryptjs";
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const { password } = await request.json();
// Now this works in any runtime
const hashedPassword = await bcrypt.hash(password, 10);
return NextResponse.json({ hash: hashedPassword });
}bcryptjs works in:
The API is identical, so your existing code requires only an import change:
// Before (crashes)
import bcrypt from "bcrypt";
// After (works)
import bcrypt from "bcryptjs";Performance Consideration: bcryptjs is approximately 30% slower than native bcrypt for hashing operations. However, since bcrypt is designed to be slow (that's part of its security model), this difference is often negligible in practice. For most applications, the difference between 100ms and 130ms per hash is imperceptible.
If you need to use native bcrypt for performance reasons and your Route Handler runs exclusively on the Node.js runtime, you can configure Next.js to externalize the package:
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
serverExternalPackages: ["bcrypt"],
};
module.exports = nextConfig;This tells Webpack not to bundle bcrypt, instead leaving it as an external require that Node.js resolves at runtime. Combined with explicitly setting the Node.js runtime for your route:
// app/api/auth/route.ts
import bcrypt from "bcrypt";
import { NextResponse } from "next/server";
// Explicitly use Node.js runtime
export const runtime = "nodejs";
export async function POST(request: Request) {
const { password } = await request.json();
const hashedPassword = await bcrypt.hash(password, 10);
return NextResponse.json({ hash: hashedPassword });
}Important Caveats:
If you're on Next.js 15.5 or later, you can configure your Middleware/Proxy to use the Node.js runtime:
// middleware.ts (or proxy.ts in Next.js 16+)
import bcrypt from "bcrypt";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
// Enable Node.js runtime for middleware
export const config = {
runtime: "nodejs",
matcher: "/api/:path*",
};
export function middleware(request: NextRequest) {
// Now bcrypt works here
// (Though you'd typically verify hashes, not create them in middleware)
return NextResponse.next();
}Note that in Next.js 16, middleware was renamed to "Proxy" and the function export changed from middleware to proxy.
For complex applications, you might want to isolate bcrypt usage to a dedicated internal API route that other parts of your application call:
// app/api/internal/hash/route.ts
import bcrypt from "bcrypt";
import { NextResponse } from "next/server";
export const runtime = "nodejs";
// Internal API - not exposed publicly
export async function POST(request: Request) {
const { password, hash, action } = await request.json();
if (action === "hash") {
const result = await bcrypt.hash(password, 12);
Then call this from other Route Handlers or Server Actions:
// app/api/auth/login/route.ts
import { NextResponse } from "next/server";
export async function POST(request: Request) {
const { email, password } = await request.json();
// Fetch user from database...
const user = await getUserByEmail(email);
// Use internal API for hash comparison
const hashResponse = await fetch(new URL("/api/internal/hash", request.url), {
method: "POST",
headers: { "Content-Type"
This pattern adds latency but provides complete isolation of native module concerns.
Prevention is always better than debugging production crashes. Here are best practices to avoid Next.js bcrypt crashes and similar native module issues:
When choosing packages for authentication and cryptography, prefer pure JavaScript implementations unless you have specific performance requirements:
| Native Package | Pure JS Alternative | Use Case |
|---|---|---|
bcrypt | bcryptjs | Password hashing |
argon2 | argon2-browser | Password hashing |
sharp | @vercel/og | Image processing |
canvas | puppeteer | Image generation |
Before adding a new package, check if it has native dependencies:
# Check if package has native bindings
npm info bcrypt | grep binding
npm info bcryptjs | grep binding # Should return nothingOr examine the package's package.json for node-gyp, prebuild, or node-pre-gyp references.
Add a simple test Route Handler with Edge runtime to catch incompatibilities early:
// app/api/edge-test/route.ts
export const runtime = "edge";
export async function GET() {
// Import your auth utilities here
// If they have native dependencies, this will fail during build
return new Response("Edge runtime working");
}In your codebase, clearly document which Route Handlers require specific runtimes:
// app/api/auth/route.ts
/**
* Authentication Route Handler
*
* @runtime nodejs - Required for bcrypt native bindings
* @see https://github.com/vercel/next.js/issues/46493
*/
export const runtime = "nodejs";Consider using libraries specifically designed for Edge and serverless environments:
Example using Web Crypto for password hashing (PBKDF2):
// utils/crypto.ts
export async function hashPassword(password: string): Promise<string> {
const encoder = new TextEncoder();
const salt = crypto.getRandomValues(new Uint8Array(16));
const keyMaterial = await crypto.subtle.importKey(
"raw",
encoder.encode(password),
"PBKDF2",
false,
["deriveBits"]
);
const hash
The crash occurs because bcrypt uses native C++ bindings that Webpack cannot bundle for Edge or browser runtimes, causing parse errors during build.
bcryptjs is the fastest fix—it's a drop-in replacement with identical API that works in all Next.js runtimes without configuration changes.
serverExternalPackages configuration can work for Route Handlers on Node.js runtime but doesn't solve the Middleware/Edge runtime problem.
Next.js 15.5+ supports Node.js runtime for Middleware, enabling native modules if you explicitly configure runtime: 'nodejs'.
Prevention is key—prefer pure JavaScript packages for authentication and cryptography to avoid runtime compatibility issues entirely.
Audit your dependencies: Run npm ls and check for any packages with native bindings that might cause similar issues.
Migrate to bcryptjs: If you're experiencing crashes, swap bcrypt for bcryptjs as your first fix.
Test your build in CI: Add Edge runtime tests to your CI pipeline to catch native module issues before deployment.
Consider upgrading to Next.js 15.5+: If you need native modules in Middleware, the Node.js runtime support provides a proper solution.
Review the official guides: The Next.js Route Handlers documentation and Middleware documentation provide authoritative guidance on runtime configuration.
By understanding the fundamental incompatibility between native Node.js modules and Edge runtimes, you can make informed decisions about your authentication architecture and avoid the frustrating Next.js Route Handlers bcrypt crash entirely.