How to Fix: next-typesafe-url lib causes “ReferenceError: require is not defined” since canary v150 in dev mode with turbo

6 min read

The crash is triggered by a CommonJS runtime path leaking into a Turbopack dev build where the browser/client side expects pure ESM. After Next.js canary v15.0.0 behavior changes, next-typesafe-url can end up executing code that still calls require, and in the browser that becomes the fatal error: ReferenceError: require is not defined.

Understanding the Root Cause

This issue appears when three conditions combine:

  1. Next.js canary changes module handling in dev mode.
  2. Turbopack is used with next dev --turbo.
  3. next-typesafe-url or one of its generated/runtime imports resolves to code that contains require(...).

In older or non-turbo flows, the bundler may silently transform or tolerate some CommonJS usage. In Turbopack dev mode, client modules are evaluated more strictly as browser-compatible modules. That means a direct require call is no longer available unless the code is transformed before reaching the client bundle.

Technically, the failure usually comes from one of these patterns:

  • A package publishes mixed CJS/ESM outputs, and the wrong export condition is selected.
  • A file intended for server-only use is imported from a client boundary.
  • A generated helper or validation utility pulls in a dependency that still emits require.
  • Turbopack resolves package entry points differently from the classic webpack dev pipeline.

The key point is that the problem is usually not that require is globally broken. The real issue is that a module containing Node-style runtime code is being evaluated in a browser/client context.

Step-by-Step Solution

The most reliable fix is to ensure that next-typesafe-url stays on an ESM-safe path and that any server-only imports do not cross into client components.

1. Confirm the failing import path

Start the dev server with Turbopack and inspect the stack trace carefully.

pnpm next dev --turbo

Look for the first file in your app that imports from next-typesafe-url or a generated route helper. If that file is a client component, marked with 'use client', that is the first red flag.

2. Move route parsing and validation out of client components

If you are importing server-capable helpers inside a client component, move that logic into a server component, route handler, or plain shared utility that only uses browser-safe exports.

// Bad: client component pulling in a package path that may resolve to CJS/runtime code
'use client'

import { parseRoute } from 'next-typesafe-url'

export function ClientWidget() {
  const data = parseRoute('/dashboard')
  return <div>{data.route}</div>
}
// Better: keep parsing on the server side
import { parseRoute } from 'next-typesafe-url'
import ClientWidget from './ClientWidget'

export default function Page() {
  const data = parseRoute('/dashboard')
  return <ClientWidget route={data.route} />
}
'use client'

export default function ClientWidget({ route }: { route: string }) {
  return <div>{route}</div>
}

3. Prefer ESM imports only

If your codebase or generated files use require, convert them to ES module imports.

// Replace this
const lib = require('next-typesafe-url')

// With this
import * as lib from 'next-typesafe-url'

If a generated file is using CommonJS, regenerate it after upgrading the library, or patch the file temporarily until the upstream package is fixed.

4. Check package versions for a known compatibility mismatch

Update to the latest versions of the relevant packages first:

pnpm up next next-typesafe-url react react-dom

If the issue started only after adopting a specific canary release, pin Next.js to the last known working version while keeping the reproduction stable.

pnpm add next@canary

Or pin to a previously working canary or stable release:

pnpm add next@15.0.0-canary.149

This is not the ideal long-term fix, but it is a practical unblocker while waiting for an upstream patch in either Next.js or next-typesafe-url.

5. Force transpilation of the package in Next.js

If the package ships code that Turbopack is not transforming correctly, tell Next.js to transpile it.

// next.config.js or next.config.mjs
const nextConfig = {
  transpilePackages: ['next-typesafe-url'],
}

export default nextConfig

This can help when the package publishes modern or mixed output that needs consistent transformation in your app.

6. Avoid importing Node-only dependencies from shared route utilities

If your route layer pulls in validation, file system, or process-dependent code, split those imports.

// shared/routes.ts
export type RouteInfo = {
  route: string
}

export function getRouteInfo(path: string): RouteInfo {
  return { route: path }
}
// server/routes.server.ts
import { getRouteInfo } from './shared/routes'
import 'server-only'

export function getValidatedRoute(path: string) {
  return getRouteInfo(path)
}

This keeps browser-safe logic separate from Node-only runtime code.

7. Test with and without Turbopack

Verify whether the bug is specific to Turbopack.

pnpm next dev
pnpm next dev --turbo

If the error only happens with --turbo, that strongly supports an export-resolution or transformation issue rather than a general app bug.

8. Apply a temporary workaround if you need immediate stability

If you cannot refactor immediately, use one of these short-term mitigations:

  • Run dev without Turbopack.
  • Pin Next.js to the last non-breaking canary.
  • Patch the package entry point to use ESM-compatible exports only.
pnpm next dev

For teams blocked in active development, this is often the fastest path until the upstream ecosystem catches up.

// next.config.mjs
const nextConfig = {
  transpilePackages: ['next-typesafe-url'],
}

export default nextConfig
// app/page.tsx
import { parseRoute } from 'next-typesafe-url'

export default function Page() {
  const route = parseRoute('/settings')
  return <div>{route.route}</div>
}

If you need to track the original bug report or compare reproduction details, review the reproduction repository.

Common Edge Cases

  • Client component contamination: A parent or sibling module marked with 'use client' can pull shared imports into the client graph unexpectedly.
  • Conditional exports mismatch: A package may expose import, require, browser, and default conditions, and Turbopack may resolve a different branch than webpack did.
  • Generated files: Code generation tools sometimes emit CommonJS helpers even in an ESM app.
  • Transitive dependencies: The visible import may be ESM, but one nested dependency still calls require.
  • Monorepo symlinks: In workspaces, local packages may bypass normal transpilation and surface raw source files that were never converted for browser use.
  • Server-only packages in shared code: Even a harmless-looking utility can break if it imports Node APIs like fs, path, or libraries expecting a Node runtime.

FAQ

Why does this only happen in dev mode with --turbo?

Because Turbopack uses a different module graph and transformation pipeline from the classic dev bundler. That can expose CJS/ESM boundary problems that were previously hidden.

Is next-typesafe-url definitely broken?

Not always. The issue can come from how the package is resolved, where it is imported, or how a nested dependency is bundled. The library may work in webpack dev, production builds, or server components while still failing in a client-side Turbopack path.

What is the safest long-term fix?

Keep route parsing and validation in server components or browser-safe shared utilities, ensure all consumed package paths are ESM-compatible, and upgrade once the upstream compatibility fix lands in either Next.js or next-typesafe-url.

Leave a Reply

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