← writing

Fixing a circular dependency in bits-ui that broke SSR

November 15, 2025

I was working on a SvelteKit project that used bits-ui for headless UI components. Everything worked fine in the browser, but on every SSR render the server was throwing:

TypeError: Cannot read properties of undefined (reading 'Root')

The stack trace pointed into bits-ui internals. The component was defined — the TypeScript types were fine — but at runtime on the server, the import was resolving to undefined.

Finding the cause

The issue was a circular dependency created by barrel-file imports. The library was exporting everything through a single $lib/index.js entry point, and components were importing from that same barrel file. During SSR, Node.js evaluates modules synchronously. When module A imports from the barrel, and the barrel imports from module A (transitively), one of them gets an incomplete module object — undefined — at the point it's needed.

The browser doesn't hit this because it uses a different module evaluation strategy that handles cycles more gracefully. SSR exposes it immediately.

The fix

The solution was to replace barrel-file imports inside the library with direct module references. Instead of:

import { Dialog } from '$lib/index.js';

Import the module directly:

import { Dialog } from '$lib/components/dialog/index.js';

This breaks the cycle. Each module now has a clear, acyclic dependency path, so SSR evaluation completes without hitting an undefined intermediate.

The broader pattern

Barrel files are convenient for consumers of a library, but they create risk when used internally. Inside a library, prefer direct imports. Reserve the barrel for the public API surface — the thing your users import from.

If you're debugging an SSR crash where something is undefined despite being correctly typed, circular dependencies through barrel files are worth checking early. A quick way to confirm: temporarily replace a suspected barrel import with the direct path and see if the crash disappears.