Another entry in the bugs that do not crash family.
In the App Router, wrapping a cross-origin URL in next/link can fail without a
trace. The click is intercepted, the client router tries to resolve a route it
does not own, and then nothing happens.
- Symptom
- The sidebar item lights up as active. The content area goes blank. No navigation, no console error, no 404.
- Cause
next/linkintercepted the click for a cross-origin href the client router cannot resolve.- Fix
- Branch on the href shape and render external links as a plain anchor.
How it got in
The sidebar renders nav items from a config array. Adding an external item felt free:
// navigation.ts
{ id: 'notes', label: 'Notes', href: 'https://ariefrsee.github.io/notes/' }
Every item went through the same <Link>. Internal routes worked, so the pattern
looked proven. Nobody clicked the one external item before shipping, because why
would you click the nav item you just added to a list of nav items that already
work.
The fix
- <Link href={item.href}>{item.label}</Link>
+ {isExternalHref(item.href) ? (
+ <a href={item.href} target="_blank" rel="noopener noreferrer">{item.label}</a>
+ ) : (
+ <Link href={item.href}>{item.label}</Link>
+ )}
Two follow-on details are worth encoding rather than rediscovering.
Mobile drawers close on route change. An external link opening a new tab
never changes pathname, so the drawer stays open behind it. Wire onClick to
the close handler on the anchor explicitly.
Mark it visually. An outbound-arrow icon in the badge slot tells people they are leaving the site before they click, not after.