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.