How to Fix: Links not working in only some components
Your Link looks correct, but clicks fail only in certain cards because the link is not actually the top clickable layer. In this portfolio layout, animated overlays, absolute-positioned elements, transforms, or parent wrappers can sit above the anchor and intercept pointer events, making the component appear clickable while the browser never reaches the navigation element.
Table of Contents
Understanding the Root Cause
When a Next.js Link works in some components but not others, the problem is usually not routing itself. It is most often caused by one of these UI-layer issues:
- An absolutely positioned element covers the link.
- A parent container uses z-index or transform in a way that creates a new stacking context.
- An image, overlay, or animation layer captures the click before the anchor does.
- A custom component visually looks like a button, but the actual clickable element is nested incorrectly.
- pointer-events behavior disables clicks on the link or enables clicks on a covering element.
In card-based UI components like ProjectCard.tsx, this commonly happens when the card has decorative layers such as gradients, hover effects, background glows, or animated wrappers. If one of those layers is positioned above the link, only some cards may fail depending on their DOM structure, content height, or conditional rendering.
Another common source of confusion is mixing a clickable card with nested interactive children. For example, wrapping a whole card in a link while also placing buttons, motion layers, or nested anchors inside can produce inconsistent behavior.
Step-by-Step Solution
The safest fix is to make the intended clickable element the top interaction layer and ensure decorative layers cannot capture pointer events.
1. Inspect the rendered card structure
Open browser DevTools and inspect the non-working card. Hover over the element tree and confirm whether the visible button or link is actually on top. If another element highlights above it, that layer is blocking the click.
2. Disable pointer capture on decorative overlays
If your card contains absolute-positioned visual layers, add pointer-events-none to those elements.
<div className="absolute inset-0 pointer-events-none">
{/* glow, overlay, animation, particles, gradients */}
</div>
This tells the browser to ignore that layer during hit testing so the click passes through to the real link.
3. Ensure the link has a higher stacking order
Place the link in a relatively positioned layer with a higher z-index.
<div className="relative">
<div className="absolute inset-0 pointer-events-none">
{/* background visuals */}
</div>
<div className="relative z-10">
<Link href="/projects/my-project" className="inline-block">
View Project
</Link>
</div>
</div>
4. Avoid nesting interactive elements incorrectly
Do not put a button inside a link or a link inside another link. Use one primary interactive element.
Incorrect:
<Link href="/project">
<button>Open</button>
</Link>
Better:
<Link href="/project" className="btn">
Open
</Link>
5. If the whole card should be clickable, wrap the card content cleanly
Instead of placing a tiny link inside multiple layers, make the entire card the link and keep overlays non-interactive.
import Link from "next/link";
export default function ProjectCard() {
return (
<Link href="/projects/space-theme" className="block relative rounded-xl overflow-hidden">
<div className="absolute inset-0 pointer-events-none">
{/* visual effects */}
</div>
<div className="relative z-10 p-4">
<h3>Space Theme Project</h3>
<p>Open the project details.</p>
</div>
</Link>
);
}
6. If using Framer Motion or animated wrappers, verify event behavior
Animation libraries can introduce wrappers with transforms or overlays. Keep motion containers non-blocking unless they are the intended click target.
<motion.div className="relative">
<div className="absolute inset-0 pointer-events-none" />
<Link href="/projects/demo" className="relative z-10">
Visit Demo
</Link>
</motion.div>
7. Apply the fix to the problematic card component
For a component like ProjectCard.tsx, the corrected pattern typically looks like this:
import Link from "next/link";
interface ProjectCardProps {
title: string;
description: string;
href: string;
}
export default function ProjectCard({ title, description, href }: ProjectCardProps) {
return (
<div className="relative overflow-hidden rounded-xl">
<div className="absolute inset-0 pointer-events-none">
{/* gradients / stars / hover overlays */}
</div>
<div className="relative z-10 p-4">
<h3 className="font-semibold">{title}</h3>
<p>{description}</p>
<Link href={href} className="inline-flex items-center mt-4">
View Project
</Link>
</div>
</div>
);
}
If you want to verify the original file structure, inspect the component on GitHub and compare its positioned layers, hover effects, and wrapper hierarchy against the pattern above.
8. Debug quickly with temporary CSS changes
If you are unsure which layer is blocking interaction, temporarily remove overlays or add visible outlines while testing.
.debug-layer {
outline: 2px solid red;
}
.debug-link {
outline: 2px solid lime;
}
Then test whether the link starts working after removing one overlay at a time.
Common Edge Cases
- Parent element has onClick logic: A parent click handler may call preventDefault() or interfere with navigation.
- Nested anchors: Invalid HTML can make browser behavior inconsistent across components.
- Overflow and transforms: Elements with overflow-hidden, translate, or scale can create confusing hit areas.
- Invisible elements: An element with opacity 0 can still block clicks if it remains above the link.
- Disabled pointer events on the wrong element: If pointer-events-none is applied to the link or its parent content layer, nothing inside becomes clickable.
- Client/server mismatch: Less common, but hydration issues can make interactive components behave unpredictably if the rendered structure changes between server and client.
- Using href incorrectly: Internal routes should use valid app paths, while external destinations may need a normal anchor with target and rel attributes.
FAQ
Why does the same Link work in one card but not another?
Because the issue is usually structural, not routing-related. One card may have an extra overlay, different conditional content, or a stacking context that places another element above the link.
Should I use a button instead of Link?
Use Link for navigation and button for actions like opening a modal or triggering local state changes. Replacing a broken link with a button does not solve the real layering problem.
How do I know if an overlay is blocking clicks?
Inspect the card in DevTools and hover through the DOM layers. If a full-size absolute element sits above the link, add pointer-events-none to that layer or lower its stacking order.
The core fix is simple: keep visual effects decorative, keep the Link on the top interactive layer, and avoid invalid nesting. Once the stacking order and pointer event flow are corrected, the affected components behave consistently across the entire UI.