The Crash
Every page except the homepage threw "Something went wrong!", which is the root error boundary. There were no build errors, no type errors, and no lint warnings; the app compiled cleanly and the homepage rendered fine. Navigate anywhere else, though, and the entire React tree unmounted.
The error was 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 it
had no business being undefined.
The answer turned out to be 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, so it doesn't exist at runtime, but
Turbopack doesn't know that at module evaluation time. A value import tells
the bundler "this module has 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 very module that hasn't finished initializing yet.
The result is that BLOG_LINK is undefined by the time blogThumbnails.ts reads it.
The homepage survived because its render path didn't touch CommandPalette
early enough to trip the cycle; every other page hit it right away.
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
ever sees it, so there's no module evaluation, no dependency edge, and no cycle. I applied
the same fix to the two other files that were importing types from types/base.ts
as 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, and every page renders again.
Why This Wasn't Caught Earlier
Webpack resolved the cycle differently; it happened to evaluate the modules
in an order that worked. Turbopack, which Next.js dev mode uses, evaluates
more eagerly, so it exposes cycles that webpack silently tolerated. The ESLint
import/no-cycle rule I added later would have caught this, but it
wasn't on 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 and type imports don't, and in a codebase with enough
modules that distinction is the difference between "it works" and
"every page crashes."
TypeScript's verbatimModuleSyntax flag enforces this for you automatically, and if
you're not using it yet, the next circular dependency crash will probably talk you into it.
