How to Fix: please see https://github.com/nodejs/node/issues/56594
OpenZiti Node SDK breaks in a Next.js app because Next.js bundles code for both the server and the browser, while the OpenZiti Node SDK depends on Node.js-only runtime APIs that cannot execute inside client-side or Edge runtimes.
If you create a fresh Next.js app, install the OpenZiti Node SDK, and import it the same way you would in a plain Node application, the build or runtime can fail with missing core modules, unsupported globals, or execution errors caused by mismatched environments. This issue becomes more visible with newer Node.js and Next.js setups because bundlers now enforce server/client boundaries more aggressively.
Understanding the Root Cause
The core problem is not usually a single bug in your application code. It is an environment mismatch.
Next.js supports multiple execution targets:
- Client components running in the browser
- Server components running on the Node.js server
- Route handlers / API routes running either in Node.js or Edge runtime
- Middleware usually running in the Edge runtime
The OpenZiti Node SDK is designed for a real Node.js runtime. That means it may require:
- Node core modules such as net, tls, fs, or crypto
- Access to process-level APIs
- Long-lived sockets or transport behavior not available in browser sandboxes
- Native or low-level networking assumptions that do not exist in the Edge runtime
When that SDK is imported into the wrong part of a Next.js app, one of these things happens:
- The browser bundle tries to include Node-only code and fails during build.
- The Edge runtime executes code that expects Node APIs and throws at runtime.
- Server/client boundary analysis pulls a transitive import into the wrong bundle.
- Webpack or Turbopack reports unresolved modules because browser polyfills for Node core packages are no longer automatically injected.
In practical terms, the issue appears when a developer follows a normal “import and use” pattern in a Next.js boilerplate app without isolating the SDK to a Node-only execution path.
This is why the fix is architectural: keep the OpenZiti SDK on the server side only, disable Edge execution for that path, and avoid importing it from any client component.
Step-by-Step Solution
The safest pattern is to use the OpenZiti Node SDK only inside a Next.js API route or route handler configured for the Node.js runtime.
1. Install the SDK in your Next.js app
npm install @openziti/ziti-sdk-nodejs
If your package name differs in your environment, use the package documented by the OpenZiti project you are integrating.
2. Do not import the SDK in client components
This is wrong if the file is used by the browser:
'use client'";
import { ziti } from '@openziti/ziti-sdk-nodejs';
export default function Page() {
return <div>This will likely fail</div>;
}
A file marked with ‘use client’ must stay browser-safe. The OpenZiti Node SDK is not browser-safe.
3. Move OpenZiti logic into a Node-only route handler
For the App Router, create a route like this:
app/api/ziti/route.ts
export const runtime = 'nodejs';
import { NextResponse } from 'next/server';
export async function GET() {
const openziti = await import('@openziti/ziti-sdk-nodejs');
// Example placeholder: replace with your real SDK setup
// const context = await openziti.someInitMethod({ ... });
return NextResponse.json({
ok: true,
message: 'OpenZiti loaded in Node.js runtime only'
});
}
Two details matter here:
- runtime = ‘nodejs’ prevents accidental Edge execution
- dynamic import helps ensure the package is loaded only when the server route runs
4. Call that API route from your client component
'use client';
import { useEffect, useState } from 'react';
export default function Page() {
const [data, setData] = useState(null);
const [error, setError] = useState('');
useEffect(() => {
fetch('/api/ziti')
.then((res) => res.json())
.then(setData)
.catch((err) => setError(String(err)));
}, []);
if (error) return <p>Error: {error}</p>;
if (!data) return <p>Loading...</p>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
This keeps your browser code clean while delegating all OpenZiti work to the server.
5. If using the Pages Router, use an API route
pages/api/ziti.ts
import type { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const openziti = await import('@openziti/ziti-sdk-nodejs');
res.status(200).json({
ok: true,
message: 'OpenZiti loaded in Node.js API route'
});
}
6. Prevent accidental shared imports
A common mistake is placing OpenZiti logic in a shared utility file used by both server and client code.
Avoid this:
lib/ziti.ts
import '@openziti/ziti-sdk-nodejs';
export function doSomething() {
// ...
}
If lib/ziti.ts gets imported by a client component, the build can still fail.
Use a server-only module instead:
lib/ziti.server.ts
import 'server-only';
export async function getZitiSdk() {
return import('@openziti/ziti-sdk-nodejs');
}
Then consume it only from server files:
app/api/ziti/route.ts
export const runtime = 'nodejs';
import { NextResponse } from 'next/server';
import { getZitiSdk } from '@/lib/ziti.server';
export async function GET() {
const sdk = await getZitiSdk();
return NextResponse.json({ ok: true, loaded: !!sdk });
}
7. If the package still gets bundled incorrectly, mark it as server external
In some setups, especially with complex monorepos or experimental bundlers, you may need to guide Next.js explicitly.
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverComponentsExternalPackages: ['@openziti/ziti-sdk-nodejs']
}
};
module.exports = nextConfig;
For newer Next.js versions, this option may be replaced or renamed depending on the release line. Check the Next.js documentation for your exact version.
8. Verify your runtime assumptions
If you are deploying to a platform that defaults some routes to Edge, explicitly force Node.js where needed.
export const runtime = 'nodejs';
Do not attempt to use the OpenZiti Node SDK in:
- middleware.ts
- Edge API routes
- client components
- browser event handlers
9. Minimal working structure
app/
api/
ziti/
route.ts
page.tsx
lib/
ziti.server.ts
next.config.js
This separation is usually enough to resolve the issue cleanly.
Common Edge Cases
1. The import is indirect
You may think the SDK is only used on the server, but a shared helper imports it transitively. Next.js follows the import graph, so one accidental shared dependency can break the client bundle.
Fix: move all OpenZiti code into clearly named server-only modules such as *.server.ts.
2. Middleware crashes even though API routes work
Middleware runs in the Edge runtime, not standard Node.js. Even if the same code works in an API route, it can fail in middleware.
Fix: do not initialize the OpenZiti Node SDK in middleware.
3. Build passes locally but fails in deployment
Some cloud platforms use different runtime defaults, stricter bundling, or smaller execution environments.
Fix: explicitly declare runtime = ‘nodejs’, test production builds locally with next build, and verify your host supports the required Node APIs.
4. Native dependency or networking restrictions
If the SDK relies on lower-level networking or environment-specific capabilities, serverless platforms may limit socket behavior, filesystem access, or process lifetime.
Fix: test on a full Node server first, then validate compatibility with your target hosting platform.
5. Version mismatch between Node.js, Next.js, and the SDK
The linked issue title points at Node.js, but the visible behavior may be triggered by a combination of framework bundling rules and package expectations.
Fix: validate your exact versions, test with the active LTS Node.js release, and compare against the SDK’s documented compatibility matrix if available.
6. Top-level initialization causes startup failures
If you initialize the SDK at module load time, any configuration problem can crash the route before the request lifecycle begins.
Fix: initialize lazily inside the handler or in a guarded singleton.
let sdkPromise: Promise<any> | null = null;
export function getSdk() {
if (!sdkPromise) {
sdkPromise = import('@openziti/ziti-sdk-nodejs');
}
return sdkPromise;
}
FAQ
Can I use the OpenZiti Node SDK directly in a Next.js client component?
No. Client components run in the browser, and the OpenZiti Node SDK expects a Node.js runtime. Use a server route as a bridge.
Why does dynamic import help here?
Dynamic import does not magically make a Node-only package browser-compatible, but it helps keep loading scoped to a server-only code path and reduces accidental eager bundling.
Is this a Node.js bug or a Next.js bug?
Most often it is a runtime compatibility issue between a Node-only SDK and the mixed execution model of Next.js. Node.js may appear in the stack trace, but the real fix is usually to keep the package out of browser and Edge bundles.
The practical resolution is simple: treat the OpenZiti SDK as server-only infrastructure code. Once you isolate it behind a Node.js API boundary, the Next.js app can interact with it safely without pulling unsupported networking logic into the browser or Edge runtime.