Navigation is fundamental to any web application. The Next.js Link component provides client-side navigation that's faster than traditional page loads, with automatic prefetching for instant page transitions. Unlike regular <a> tags that reload the entire page, Link components navigate smoothly while preserving client-side state and avoiding unnecessary re-renders. Let's master the Link component!
Why Use Link Component?
❌ Regular <a> Tag
// Traditional navigation
<a href="/blog">Blog</a>
// Problems:
// ❌ Full page reload
// ❌ JavaScript re-downloads
// ❌ Client state lost
// ❌ Slow navigation
// ❌ No prefetching✅ Next.js Link Component
import Link from 'next/link';
// Client-side navigation
<Link href="/blog">Blog</Link>
// Benefits:
// ✅ No page reload
// ✅ Instant navigation
// ✅ Preserves state
// ✅ Automatic prefetching
// ✅ Faster experiencePerformance Impact
- Regular <a>: 1-2 seconds full page load
- Link component: ~50ms instant navigation (with prefetch)
- Improvement: 20-40x faster navigation!
Basic Link Usage
Simple Link
import Link from 'next/link';
export function Navbar() {
return (
<nav className="flex gap-6 p-4 bg-gray-100">
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/blog">Blog</Link>
<Link href="/contact">Contact</Link>
</nav>
);
}
// ✅ Import Link from 'next/link'
// ✅ Use href prop for the destination
// ✅ Link renders as <a> tag in HTML
// ✅ Navigation is client-side (no page reload)Link with Styling
import Link from 'next/link';
export function Navbar() {
return (
<nav className="flex gap-6 p-4 bg-gray-100">
<Link
href="/"
className="text-blue-600 hover:text-blue-800 font-semibold transition"
>
Home
</Link>
<Link
href="/blog"
className="text-blue-600 hover:text-blue-800 font-semibold transition"
>
Blog
</Link>
</nav>
);
}
// ✅ Add className directly to Link
// ✅ All standard HTML attributes work
// ✅ Hover effects, transitions, etc.Link with Children
import Link from 'next/link';
export function BlogCard({ post }: { post: Post }) {
return (
<Link href={/blog/${post.slug}}>
<article className="border rounded-lg p-6 hover:shadow-lg transition cursor-pointer">
<h2 className="text-2xl font-bold mb-2">{post.title}</h2>
<p className="text-gray-600 mb-4">{post.excerpt}</p>
<span className="text-blue-600 font-semibold">Read more →</span>
</article>
</Link>
);
}
// ✅ Entire card is clickable
// ✅ Wraps complex children
// ✅ Semantic and accessibleDynamic Links
Template Literals
import Link from 'next/link';
interface Post {
id: string;
slug: string;
title: string;
excerpt: string;
}
async function BlogPage() {
const posts: Post[] = await fetch('https://api.example.com/posts')
.then(r => r.json());
return (
<div className="container mx-auto px-4 py-8">
<h1 className="text-3xl font-bold mb-8">Blog Posts</h1>
<div className="space-y-6">
{posts.map(post => (
<article key={post.id} className="border rounded-lg p-6">
<Link href={/blog/${post.slug}}>
<h2 className="text-2xl font-semibold mb-2 hover:text-blue-600 transition">
{post.title}
</h2>
</Link>
<p className="text-gray-700 mb-4">{post.excerpt}</p>
<Link
href={/blog/${post.slug}}
className="text-blue-600 hover:underline"
>
Read more →
</Link>
</article>
))}
</div>
</div>
);
}
export default BlogPage;
// ✅ Dynamic href with template literals
// ✅ Each post links to its unique page
// ✅ Type-safe with TypeScriptObject-Style href
import Link from 'next/link';
// Link with query parameters
<Link
href={{
pathname: '/blog',
query: { category: 'tech', page: '1' },
}}
>
Tech Blog
</Link>
// Navigates to: /blog?category=tech&page=1
// Link with hash
<Link
href={{
pathname: '/docs',
hash: 'installation',
}}
>
Installation
</Link>
// Navigates to: /docs#installation
// Complex example
<Link
href={{
pathname: '/search',
query: {
q: 'next.js',
category: 'tutorials',
sort: 'recent'
},
}}
>
Search Tutorials
</Link>
// Navigates to: /search?q=next.js&category=tutorials&sort=recent
// ✅ Object format for complex URLs
// ✅ Query parameters automatically encoded
// ✅ Type-safe pathnamePrefetching
Next.js automatically prefetches linked pages when Links enter the viewport:
How Prefetching Works
- Link enters viewport - Next.js detects the Link
- Background fetch - Page data is fetched in the background
- Data cached - Fetched data is stored in Router Cache
- User clicks - Navigation is instant (data already loaded)
Default Prefetching Behavior
import Link from 'next/link';
// Prefetching ON by default (production only)
<Link href="/blog">Blog</Link>
// Explicitly enable prefetching
<Link href="/blog" prefetch={true}>
Blog
</Link>
// Disable prefetching
<Link href="/admin" prefetch={false}>
Admin Dashboard
</Link>
// ✅ Prefetch=true (default): Prefetches automatically
// ✅ Prefetch=false: Only fetches on click
// ⚠️ Prefetching only happens in production, not developmentWhen to Disable Prefetching
// Disable prefetching for:
// 1. Authenticated/protected routes
<Link href="/dashboard" prefetch={false}>
Dashboard
</Link>
// 2. Less likely to be clicked
<Link href="/privacy-policy" prefetch={false}>
Privacy Policy
</Link>
// 3. External or dynamic content
<Link href="/api/download" prefetch={false}>
Download Report
</Link>
// 4. Large pages (to save bandwidth)
<Link href="/massive-gallery" prefetch={false}>
Full Gallery
</Link>
// ✅ Saves bandwidth
// ✅ Reduces unnecessary requests
// ✅ Better for authenticated routes⚠️ Prefetching in Development
Prefetching is disabled in development mode to avoid too many requests during development. Test prefetching behavior in production builds.
Special Link Cases
External Links
// ❌ BAD: Using Link for external URLs
import Link from 'next/link';
<Link href="https://google.com">Google</Link>
// Works, but unnecessary - Link is for internal navigation
// ✅ GOOD: Use regular <a> for external links
<a
href="https://google.com"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline"
>
Google
</a>
// ✅ External links should:
// - Use <a> tag
// - Have target="_blank" to open in new tab
// - Have rel="noopener noreferrer" for security
// - Not use Link componentReplace vs Push
import Link from 'next/link';
// Default: Push to history (can go back)
<Link href="/new-page">Go to New Page</Link>
// Replace: Replace current history entry (can't go back)
<Link href="/new-page" replace>
Go to New Page
</Link>
// Use cases for replace:
// ✅ Login redirects: <Link href="/dashboard" replace>
// ✅ Step-by-step flows where you don't want back button
// ✅ Replacing temporary/intermediate pages
// Example: After login, replace login page with dashboard
<Link href="/dashboard" replace>
Continue to Dashboard
</Link>
// User can't click back to login pageScroll Behavior
import Link from 'next/link';
// Default: Scroll to top on navigation
<Link href="/about">About</Link>
// Preserve scroll position
<Link href="/about" scroll={false}>
About
</Link>
// Use cases for scroll={false}:
// ✅ Pagination: Keep scroll position when loading more
// ✅ Filter changes: Don't scroll to top on filter
// ✅ Tab switching: Stay at current scroll
// Example: Pagination
<Link
href={/blog?page=${currentPage + 1}}
scroll={false}
>
Next Page
</Link>
// User stays at same scroll positionShallow Routing (Query Parameter Updates)
import Link from 'next/link';
// Shallow: Update URL without re-running data fetching
<Link
href="/blog?sort=recent"
shallow={true}
>
Sort by Recent
</Link>
// When to use shallow:
// ✅ Updating filters without refetching data
// ✅ Changing sort order
// ✅ Updating tabs/views without data change
// Example: Filter buttons
<div className="flex gap-2">
<Link href="/blog?category=tech" shallow>
Tech
</Link>
<Link href="/blog?category=design" shallow>
Design
</Link>
</div>
// Updates URL without re-fetching page dataPractical Examples
Navigation Menu
import Link from 'next/link';
export function Navbar() {
return (
<nav className="bg-white shadow-md">
<div className="container mx-auto px-4 py-4">
<div className="flex items-center justify-between">
{/* Logo */}
<Link href="/" className="text-2xl font-bold text-blue-600">
MyApp
</Link>
{/* Navigation Links */}
<ul className="flex gap-6">
<li>
<Link
href="/"
className="text-gray-700 hover:text-blue-600 transition font-medium"
>
Home
</Link>
</li>
<li>
<Link
href="/about"
className="text-gray-700 hover:text-blue-600 transition font-medium"
>
About
</Link>
</li>
<li>
<Link
href="/blog"
className="text-gray-700 hover:text-blue-600 transition font-medium"
>
Blog
</Link>
</li>
<li>
<Link
href="/contact"
className="text-gray-700 hover:text-blue-600 transition font-medium"
>
Contact
</Link>
</li>
</ul>
{/* CTA Button */}
<Link
href="/signup"
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition font-semibold"
>
Sign Up
</Link>
</div>
</div>
</nav>
);
}
// ✅ Professional navigation bar
// ✅ All links use Link component
// ✅ Styled with Tailwind
// ✅ Hover effects and transitionsBlog Card with Link
import Link from 'next/link';
import Image from 'next/image';
interface Post {
slug: string;
title: string;
excerpt: string;
image: string;
author: {
name: string;
avatar: string;
};
publishedAt: string;
}
export function BlogCard({ post }: { post: Post }) {
return (
<article className="bg-white rounded-lg shadow-md overflow-hidden hover:shadow-xl transition">
{/* Image Link */}
<Link href={/blog/${post.slug}}>
<div className="relative h-48 w-full">
<Image
src={post.image}
alt={post.title}
fill
className="object-cover"
/>
</div>
</Link>
<div className="p-6">
{/* Title Link */}
<Link href={/blog/${post.slug}}>
<h2 className="text-2xl font-bold mb-3 hover:text-blue-600 transition">
{post.title}
</h2>
</Link>
{/* Excerpt */}
<p className="text-gray-600 mb-4 line-clamp-3">
{post.excerpt}
</p>
{/* Author Info */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Image
src={post.author.avatar}
alt={post.author.name}
width={40}
height={40}
className="rounded-full"
/>
<div>
<p className="font-semibold text-sm">{post.author.name}</p>
<p className="text-xs text-gray-500">{post.publishedAt}</p>
</div>
</div>
{/* Read More Link */}
<Link
href={/blog/${post.slug}}
className="text-blue-600 hover:underline font-semibold"
>
Read more →
</Link>
</div>
</div>
</article>
);
}
// ✅ Multiple clickable areas (image, title, button)
// ✅ All use same Link destination
// ✅ Professional card design
// ✅ Hover effects on interactive elementsBreadcrumb Navigation
import Link from 'next/link';
interface Breadcrumb {
label: string;
href: string;
}
export function Breadcrumbs({ items }: { items: Breadcrumb[] }) {
return (
<nav className="flex items-center gap-2 text-sm mb-6">
<Link
href="/"
className="text-gray-600 hover:text-blue-600 transition"
>
Home
</Link>
{items.map((item, index) => {
const isLast = index === items.length - 1;
return (
<div key={item.href} className="flex items-center gap-2">
<span className="text-gray-400">/</span>
{isLast ? (
<span className="text-gray-900 font-semibold">
{item.label}
</span>
) : (
<Link
href={item.href}
className="text-gray-600 hover:text-blue-600 transition"
>
{item.label}
</Link>
)}
</div>
);
})}
</nav>
);
}
// Usage:
// <Breadcrumbs
// items={[
// { label: 'Blog', href: '/blog' },
// { label: 'Tech', href: '/blog/tech' },
// { label: 'My Post', href: '/blog/tech/my-post' },
// ]}
// />
// ✅ Clear navigation hierarchy
// ✅ Current page not linked
// ✅ All previous levels linkedLink Component Examples
Project structure with Link component usage
Select a file or folder to see details
Link Component Best Practices
1. Always Use Link for Internal Navigation
// ✅ GOOD: Use Link for internal routes
import Link from 'next/link';
<Link href="/about">About</Link>
<Link href="/blog/my-post">My Post</Link>
// ❌ BAD: Using <a> for internal routes
<a href="/about">About</a>
// Causes full page reload, slower navigation2. Use <a> for External Links
// ✅ GOOD: Use <a> for external links
<a
href="https://github.com/yourrepo"
target="_blank"
rel="noopener noreferrer"
>
GitHub
</a>
// ❌ BAD: Using Link for external URLs
<Link href="https://github.com/yourrepo">GitHub</Link>3. Disable Prefetch for Authenticated Routes
// ✅ GOOD: Disable prefetch for protected routes
<Link href="/dashboard" prefetch={false}>
Dashboard
</Link>
<Link href="/admin" prefetch={false}>
Admin
</Link>
// Prevents prefetching protected content before login4. Make Large Click Areas
// ✅ GOOD: Entire card is clickable
<Link href="/blog/post-1">
<article className="p-6 border rounded hover:shadow-lg">
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
</Link>
// ❌ BAD: Only text is clickable
<article className="p-6 border rounded">
<Link href="/blog/post-1">
<h2>{post.title}</h2>
</Link>
<p>{post.excerpt}</p>
</article>5. Provide Visual Feedback
// ✅ GOOD: Hover effects and transitions
<Link
href="/about"
className="text-blue-600 hover:text-blue-800 hover:underline transition-colors"
>
About
</Link>
// ✅ GOOD: Interactive states
<Link
href="/blog"
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 active:scale-95 transition"
>
Blog
</Link>
// Users know it's clickableKey Takeaways
- Import from 'next/link' - always use Link component
- Client-side navigation - no page reload, instant
- Automatic prefetching - pages load in background
- href prop - string or object for destination
- Use <a> for external - Link for internal only
- Disable prefetch - for authenticated or unlikely routes
- Style with className - works like regular HTML
- 20-40x faster - compared to full page loads
What's Next?
You've mastered the Link component! Next, we'll explore the useRouter Hook for Programmatic Navigation—how to navigate programmatically in response to events like form submissions, button clicks, or API responses. You'll learn to control navigation with code!
The useRouter hook gives you full control over navigation, allowing you to navigate after user actions, with custom logic, or based on application state.
⚡ Prefetching Magic
The automatic prefetching in Link components is one of Next.js's superpowers. Pages load instantly because they're already fetched before the user clicks. This makes your app feel incredibly fast with zero extra code!