Skip to main content

Next.js Linking

Introduction

Navigation is a fundamental aspect of any web application. In traditional websites, moving between pages often triggers a full page reload, resulting in a jarring user experience. Next.js, however, offers a solution that enables seamless client-side transitions between routes, making your application feel more like a single-page app while maintaining the benefits of server-rendered pages.

In this guide, we'll explore the various methods of linking and navigation in Next.js, from basic links to advanced programmatic navigation patterns.

Next.js provides a built-in Link component that extends the HTML <a> tag, enabling client-side navigation between pages in your application.

Basic Usage

To use the Link component, first import it from 'next/link':

jsx
import Link from 'next/link';

function HomePage() {
return (
<div>
<h1>Welcome to my website</h1>
<Link href="/about">About Us</Link>
</div>
);
}

export default HomePage;

When a user clicks on "About Us", they'll be taken to the /about page without a full page reload. Next.js automatically prefetches the linked page in the background when the link appears in the viewport, making navigation feel instantaneous.

The Link component accepts several props to customize its behavior:

jsx
<Link
href="/blog/post-1" // The destination path
as="/articles/my-post" // Optional path shown in the browser URL bar
replace={false} // If true, replaces current history entry instead of adding a new one
scroll={true} // Controls whether to scroll to top after navigation
prefetch={true} // Controls whether the link should be prefetched
locale="en-US" // For internationalized routes
>
Read my blog post
</Link>

Dynamic Routes

For dynamic routes, you can pass an object to the href prop with pathname and query parameters:

jsx
<Link
href={{
pathname: '/blog/[slug]',
query: { slug: 'my-post' },
}}
>
Read my blog post
</Link>

This will navigate to /blog/my-post.

Since Next.js 13, the Link component automatically adds the <a> tag, so you can style it directly:

jsx
<Link href="/contact" className="primary-button">
Contact Us
</Link>

For older versions of Next.js, you need to manually include the <a> tag:

jsx
<Link href="/contact">
<a className="primary-button">Contact Us</a>
</Link>

Linking to External Sites

For external links, you can use regular <a> tags:

jsx
<a href="https://nextjs.org" target="_blank" rel="noopener noreferrer">
Learn more about Next.js
</a>

Next.js doesn't provide a built-in way to style active links, but you can implement this functionality using the useRouter hook:

jsx
import Link from 'next/link';
import { useRouter } from 'next/router';

function NavLink({ href, children }) {
const router = useRouter();
const isActive = router.pathname === href;

return (
<Link href={href}>
<a className={isActive ? 'active' : ''}>{children}</a>
</Link>
);
}

function Navigation() {
return (
<nav>
<NavLink href="/">Home</NavLink>
<NavLink href="/about">About</NavLink>
<NavLink href="/contact">Contact</NavLink>
</nav>
);
}

Programmatic Navigation

Sometimes you need to navigate users based on certain conditions or after completing an action (like form submission). For this, you can use the useRouter hook:

jsx
import { useRouter } from 'next/router';

function LoginForm() {
const router = useRouter();

const handleSubmit = async (event) => {
event.preventDefault();

// Process login...
const success = await login(/* credentials */);

if (success) {
// Navigate to dashboard after successful login
router.push('/dashboard');
}
};

return (
<form onSubmit={handleSubmit}>
{/* Form fields */}
<button type="submit">Login</button>
</form>
);
}

Router Methods

The router object provides several navigation methods:

jsx
// Navigate to a specific URL
router.push('/dashboard');

// Replace the current URL (doesn't add to history)
router.replace('/dashboard');

// Go back to the previous page
router.back();

// Go forward
router.forward();

// Refresh the current page
router.reload();

Passing Query Parameters

You can pass query parameters programmatically:

jsx
router.push({
pathname: '/products',
query: { category: 'electronics', sort: 'price-asc' },
});

This navigates to /products?category=electronics&sort=price-asc.

Handling Navigation Events

You can detect and respond to route changes:

jsx
import { useEffect } from 'react';
import { useRouter } from 'next/router';

function MyComponent() {
const router = useRouter();

useEffect(() => {
const handleRouteChange = (url) => {
console.log('Route changing to:', url);
// Analytics tracking or other side effects
};

router.events.on('routeChangeStart', handleRouteChange);

return () => {
router.events.off('routeChangeStart', handleRouteChange);
};
}, [router]);

// Component code...
}

Real-World Examples

E-commerce Product Navigation

jsx
import Link from 'next/link';

function ProductList({ products }) {
return (
<div className="product-grid">
{products.map((product) => (
<div key={product.id} className="product-card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>${product.price.toFixed(2)}</p>
<Link href={`/products/${product.id}`} className="view-button">
View Details
</Link>
<button
className="add-to-cart"
onClick={() => addToCart(product)}
>
Add to Cart
</button>
</div>
))}
</div>
);
}
jsx
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { useAuth } from '../context/auth';

function ProtectedPage({ children }) {
const router = useRouter();
const { user, loading } = useAuth();

useEffect(() => {
if (!loading && !user) {
// Redirect to login if not authenticated
router.push({
pathname: '/login',
query: { returnUrl: router.asPath }, // Remember the current URL
});
}
}, [user, loading, router]);

if (loading || !user) {
return <div>Loading...</div>;
}

return <>{children}</>;
}

export default function Dashboard() {
return (
<ProtectedPage>
<h1>Dashboard</h1>
<p>Welcome to your dashboard!</p>
</ProtectedPage>
);
}

Multi-step Form with Navigation

jsx
import { useState } from 'react';
import { useRouter } from 'next/router';

function MultiStepForm() {
const [step, setStep] = useState(1);
const [formData, setFormData] = useState({});
const router = useRouter();

const nextStep = () => {
setStep(step + 1);
// Update URL to reflect current step without page reload
router.push(`/form?step=${step + 1}`, undefined, { shallow: true });
};

const prevStep = () => {
setStep(step - 1);
router.push(`/form?step=${step - 1}`, undefined, { shallow: true });
};

const handleSubmit = async () => {
// Submit data
await submitFormData(formData);
router.push('/confirmation');
};

return (
<div>
<h2>Step {step} of 3</h2>

{step === 1 && (
<div>
<h3>Personal Information</h3>
{/* Form fields */}
<button onClick={nextStep}>Continue</button>
</div>
)}

{step === 2 && (
<div>
<h3>Address Details</h3>
{/* Form fields */}
<button onClick={prevStep}>Back</button>
<button onClick={nextStep}>Continue</button>
</div>
)}

{step === 3 && (
<div>
<h3>Confirmation</h3>
{/* Review information */}
<button onClick={prevStep}>Back</button>
<button onClick={handleSubmit}>Submit</button>
</div>
)}
</div>
);
}

Performance Considerations

Prefetching

Next.js automatically prefetches links that appear in the viewport by default. However, you can disable this behavior for certain links:

jsx
<Link href="/large-page" prefetch={false}>
View Large Page
</Link>

Shallow Routing

When you want to change the URL without running data fetching methods again (like getServerSideProps), you can use shallow routing:

jsx
router.push('/dashboard?tab=profile', undefined, { shallow: true });

This is useful for tab interfaces or filtering where you want to update the URL but don't need to fetch new data.

Summary

Next.js provides powerful tools for navigation in your applications:

  • The Link component enables client-side navigation between pages with automatic code-splitting and prefetching
  • Programmatic navigation through the useRouter hook gives you fine-grained control over routing
  • Router events allow you to hook into navigation lifecycle for analytics or custom behaviors
  • Shallow routing helps optimize performance when updating the URL without changing page content

By leveraging these features, you can create fast, responsive applications with smooth navigation experiences for your users.

Exercises

  1. Create a simple Next.js application with a home page, about page, and contact page. Implement navigation using the Link component.

  2. Build a dynamic product listing page that links to individual product detail pages using dynamic routes.

  3. Implement an authentication system with protected routes that redirect unauthenticated users to a login page.

  4. Create a tabbed interface that updates the URL when switching between tabs using shallow routing.

  5. Build a pagination component that navigates between pages and preserves filter settings through query parameters.

Additional Resources

Happy coding!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)