Skip to main content

When import type Isn't Optional: Fixing a Circular Dependency Crash

Apr 6, 20262 min readTypeScript, Next.js, Turbopack, Debugging

The Crash

Every page except the homepage threw "Something went wrong!" — the root error boundary. No build errors, no type errors, no lint warnings. The app compiled cleanly and the homepage rendered fine. Navigate anywhere else and the entire React tree unmounted.

The error: Cannot read properties of undefined (reading 'href').

Tracing the Cycle

The undefined value was BLOG_LINK — a constant exported from utils/constants.ts. It's used everywhere, and it's clearly defined. So why was it undefined?

The answer was a five-module circular dependency:

constants.ts ──imports NavLink──→ types/base.ts ──imports blogPageSlugs──→ data/blog.ts
      ↑                                                                         │
      └──────────── blogThumbnails.ts ←── searchIndex.ts ←── CommandPalette ←────┘

constants.ts imported NavLink from types/base.ts as a value import:

import { NavLink } from '@/types/base';

NavLink is a TypeScript interface — it doesn't exist at runtime. But Turbopack doesn't know that at module evaluation time. A value import tells the bundler "this module needs to be evaluated before mine." So Turbopack evaluates types/base.ts, which imports blogPageSlugs from data/blog.ts, which imports blogThumbnails.ts, which imports BLOG_LINK from constants.ts — the module that hasn't finished initializing yet.

Result: BLOG_LINK is undefined when blogThumbnails.ts reads it.

The homepage survived because its render path didn't touch CommandPalette early enough to trigger the cycle. Every other page hit it immediately.

The One-Line Fix

- import { NavLink } from '@/types/base';
+ import type { NavLink } from '@/types/base';

import type is guaranteed to be erased by TypeScript before the bundler sees it. No module evaluation, no dependency edge, no cycle. We applied the same fix to the two other files that imported types from types/base.ts using value imports:

// BreadCrumbs.tsx
- import { BreadCrumbsProps } from '@/types/base';
+ import type { BreadCrumbsProps } from '@/types/base';
 
// getPostDetailProps.ts
- import { NavLink } from '@/types/base';
+ import type { NavLink } from '@/types/base';

Three lines changed. Every page renders.

Why This Wasn't Caught Earlier

Webpack resolved the cycle differently — it happened to evaluate modules in an order that worked. Turbopack (used in Next.js dev mode) evaluates more eagerly, exposing cycles that webpack silently tolerated. The ESLint import/no-cycle rule we added later would have caught this, but it wasn't enabled at the time.

The Rule

Always use import type for type-only imports. It's not a style preference — it's a correctness guarantee. Value imports create module evaluation dependencies. Type imports don't. In a codebase with enough modules, that distinction is the difference between "it works" and "every page crashes."

TypeScript's verbatimModuleSyntax flag enforces this automatically. If you're not using it yet, the next circular dependency crash might convince you.