How to Fix: Next.js does not pick the correct export of dependency
Encountering the error “Next.js does not pick the correct export of dependency,” especially with browser-specific libraries like onnxruntime-web, is a common frustration for developers leveraging Next.js’s powerful Server-Side Rendering (SSR) capabilities. This often manifests as compilation errors during development or crashes on the server, precisely because Next.js attempts to execute code designed for the browser within a Node.js environment.
Table of Contents
Understanding the Root Cause
Next.js applications, by default, perform Server-Side Rendering (SSR) for initial page loads. This means your React components are first rendered into HTML on the Node.js server before being sent to the client. This offers benefits like improved performance and SEO.
The problem arises when you introduce a library that is fundamentally designed to run only in a browser environment, such as onnxruntime-web. Libraries like onnxruntime-web often rely on browser-specific APIs (e.g., window, document, Web Workers, WebAssembly APIs via the browser’s runtime) that are not available in Node.js. When Next.js’s bundler (Webpack or Turbopack) tries to process and execute these modules on the server during SSR, it encounters missing global objects or incompatible native modules, leading to crashes or incorrect module resolution.
Specifically, the issue “Next.js does not pick the correct export of dependency” often indicates that the bundler, during the server-side compilation pass, is failing to correctly identify or utilize the appropriate module entry point for the Node.js target. While many modern libraries use package.json‘s exports field to provide conditional exports for node, browser, import, or require environments, sometimes this isn’t configured perfectly by the library author, or Next.js’s build process doesn’t fully respect it for all edge cases, especially with complex dependencies like WebAssembly-backed runtimes.
The core conflict is between the server’s Node.js runtime and the browser-only requirements of your dependency. The solution is to prevent these browser-specific modules from ever being processed or executed on the server.
Step-by-Step Solution
The most robust and idiomatic solution for this problem in Next.js is to use next/dynamic with the ssr: false option. This tells Next.js to only load and render the specified component or module on the client-side, completely skipping it during the server-side rendering phase.
1. Identify the Client-Side Only Logic
Pinpoint the part of your code that directly imports or uses onnxruntime-web. This code segment needs to be isolated into its own component or module.
2. Create a Client-Only Wrapper Component
Create a new React component that encapsulates all the logic related to onnxruntime-web. For instance, if your pages/index.js directly imports it, move that usage into a new file, say components/OnnxRuntimeClient.js.
Example: components/OnnxRuntimeClient.js
// components/OnnxRuntimeClient.js
import React, { useEffect, useState } from 'react';
import * as ort from 'onnxruntime-web';
export default function OnnxRuntimeClient() {
const [message, setMessage] = useState('Loading ONNX Runtime...');
useEffect(() => {
async function runModel() {
try {
// Example: Check if onnxruntime-web is available and working
// In a real app, you'd load a model and perform inference here.
const session = await ort.InferenceSession.create('/model.onnx'); // Replace with your model path
setMessage('ONNX Runtime loaded successfully on client-side!');
console.log('ORT session created:', session);
} catch (e) {
console.error('Error loading ONNX Runtime:', e);
setMessage(`Failed to load ONNX Runtime: ${e.message}`);
}
}
runModel();
}, []);
return (
<div>
<p>{message}</p>
<!-- Add your ONNX Runtime UI elements here -->
</div>
);
}
3. Dynamically Import the Client-Only Component in Your Page/Parent Component
Now, in your original page component (e.g., pages/index.js), use next/dynamic to import OnnxRuntimeClient, ensuring that ssr: false is set.
Example: pages/index.js (or any parent component)
// pages/index.js
import dynamic from 'next/dynamic';
import Head from 'next/head';
// Dynamically import the OnnxRuntimeClient component
// with ssr: false to ensure it only loads on the client side.
const OnnxRuntimeClient = dynamic(
() => import('../components/OnnxRuntimeClient'),
{ ssr: false }
);
export default function HomePage() {
return (
<div>
<Head>
<title>ONNX Runtime with Next.js</title>
<meta name="description" content="Next.js and ONNX Runtime example" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main style="padding: 20px; font-family: sans-serif;">
<h1>Welcome to Next.js with ONNX Runtime</h1>
<p>This content is rendered universally.</p>
<!-- Render the client-only component -->
<OnnxRuntimeClient />
<p>More universal content here.</p>
</main>
</div>
);
}
By following these steps, Next.js will no longer attempt to bundle or execute onnxruntime-web (or any code within OnnxRuntimeClient.js) on the server, effectively resolving the module resolution error.
Common Edge Cases
-
Direct Browser API Access Outside Dynamic Imports: Even with
next/dynamic, if you attempt to accesswindowordocumentdirectly at the top level of a component or module that isn’t dynamically imported (or within the `pages` directory on the server), Next.js SSR will still fail. Ensure any direct browser API access is always guarded (e.g.,if (typeof window !== 'undefined') { ... }) or encapsulated within client-only components. -
Libraries with Side Effects on Import: Some libraries execute code immediately upon import that relies on browser globals. If such a library is imported anywhere in your server-side rendered code, even if not directly used, it can cause errors. The
next/dynamicapproach directly solves this by preventing the import altogether on the server. -
Incorrect Placement of
next/dynamic: Ensure that thenext/dynamiccall is at the top level of your component or page, not inside an event handler or an effect hook. Placing it incorrectly might still lead to the server attempting to resolve the module path. -
Next.js App Router (v13+): For applications using the App Router, the concept is similar but often simpler with the
"use client";directive. You would mark yourOnnxRuntimeClient.jswith"use client";at the very top. This explicitly tells Next.js to render this component and its children only on the client. For dependencies likeonnxruntime-webthat are strictly client-side, using a client component is the appropriate pattern. However, for conditional loading or complex scenarios,next/dynamicwithssr: falsestill provides finer control over when and where modules are bundled.
FAQ
Q: Why can’t Next.js just ignore browser-only code during SSR?
A: Next.js’s bundler (Webpack/Turbopack) attempts to create a unified bundle that can run in both Node.js (for SSR) and the browser (for CSR). It doesn’t inherently know which parts of a third-party library are strictly browser-only unless explicitly told. While some libraries use package.json‘s exports field to provide specific entry points for node vs. browser, this isn’t always foolproof or correctly configured for all edge cases, especially for libraries with deep browser-specific dependencies like onnxruntime-web. The ssr: false option in next/dynamic gives you direct control to prevent server-side processing of specific modules.
Q: Is next/dynamic the only solution for this type of problem?
A: While next/dynamic with ssr: false is the most common, robust, and recommended solution for application-level control, other approaches exist:
1. Conditional Imports/Requires: You could guard your imports with if (typeof window !== 'undefined') { import 'my-browser-lib'; }, but this is less clean and prone to errors if the bundler still tries to resolve the path.
2. Webpack Configuration: For advanced users, custom Webpack configurations (via next.config.js) can be used to alias or mock modules for the server build, but this is significantly more complex and brittle than next/dynamic. For the App Router, using the "use client" directive is the primary method to delineate client-side code.
Q: Does using next/dynamic with ssr: false impact my bundle size or performance?
A: It generally has a positive or neutral impact. By preventing server-side bundling of client-only code, you might reduce the size of your server bundle (though this is often negligible). More importantly, it avoids errors during the build and runtime on the server. For the client, the code is still bundled and loaded as usual, but it will be loaded dynamically, potentially improving the initial page load time if the dynamically imported component is not critical for the above-the-fold content, as its JavaScript is fetched later.