Skip to main content

Next.js Navigation

Navigation is a fundamental aspect of web applications, allowing users to move between different pages and sections. Next.js provides powerful tools for implementing smooth, client-side navigation that enhances user experience by avoiding full page refreshes. In this tutorial, we'll explore different navigation techniques in Next.js applications.

Introduction to Client-Side Navigation

In traditional websites, clicking a link causes the browser to request a new HTML page from the server, resulting in a full page refresh. This approach can feel slow and disruptive to the user experience.

Next.js, on the other hand, enables client-side navigation - when a user clicks a link, the page transition happens using JavaScript, without a full page reload. This creates a smoother, app-like experience that feels significantly faster.

The primary way to navigate between pages in Next.js is by using the built-in Link component from next/link.

Basic Usage

jsx
import Link from 'next/link';

function Navigation() {
return (
<nav>
<Link href="/">Home</Link>
<Link href="/about">About</Link>
<Link href="/products">Products</Link>
</nav>
);
}

When rendered, this creates standard <a> tags but with enhanced behavior - clicks are intercepted to enable client-side navigation.

The Link component accepts several props to customize navigation behavior:

1. Basic href

jsx
<Link href="/about">About Us</Link>

2. Dynamic Routes

jsx
<Link href={`/products/${productId}`}>View Product</Link>

3. Query Parameters

jsx
<Link href="/search?category=electronics&sort=price">
Search Electronics
</Link>

4. As Object Pattern

You can also pass an object to the href prop for more complex URLs:

jsx
<Link
href={{
pathname: '/products/[id]',
query: { id: productId, filter: 'featured' },
}}
>
Featured Product
</Link>

5. Replace

By default, clicking a Link adds a new entry to the browser's history stack. To replace the current history entry instead, use the replace prop:

jsx
<Link href="/new-page" replace>
Go without adding to history
</Link>

Adding Custom Attributes

You can include additional attributes on the underlying <a> tag:

jsx
<Link 
href="/terms"
target="_blank"
rel="noopener noreferrer"
>
Terms of Service
</Link>

There are multiple ways to style links in Next.js:

Using CSS Modules

jsx
// styles/Navigation.module.css
.navLink {
margin-right: 10px;
color: blue;
text-decoration: none;
}

.activeLink {
font-weight: bold;
color: darkblue;
}
jsx
import styles from '../styles/Navigation.module.css';
import Link from 'next/link';
import { useRouter } from 'next/router';

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

return (
<nav>
<Link
href="/"
className={router.pathname === '/' ? styles.activeLink : styles.navLink}
>
Home
</Link>
<Link
href="/about"
className={router.pathname === '/about' ? styles.activeLink : styles.navLink}
>
About
</Link>
</nav>
);
}

Using CSS-in-JS Solutions

If you're using a CSS-in-JS library like styled-components or emotion, you can create styled links:

jsx
import Link from 'next/link';
import styled from 'styled-components';

const StyledLink = styled.a`
margin-right: 15px;
color: #0070f3;
text-decoration: none;

&:hover {
text-decoration: underline;
}
`;

function Navigation() {
return (
<nav>
<Link href="/" passHref legacyBehavior>
<StyledLink>Home</StyledLink>
</Link>
<Link href="/about" passHref legacyBehavior>
<StyledLink>About</StyledLink>
</Link>
</nav>
);
}

Programmatic Navigation

Sometimes you need to navigate programmatically (e.g., after a form submission or based on some condition). Next.js provides the useRouter hook for this purpose.

Using the useRouter Hook

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

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

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

// Perform login logic
const success = await performLogin();

if (success) {
// Redirect 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 methods for navigation:

1. router.push

Navigates to the specified URL and adds an entry to the history stack:

jsx
// Navigate to a simple path
router.push('/dashboard');

// Navigate with query parameters
router.push('/products?category=electronics');

// Using URL object
router.push({
pathname: '/products/[id]',
query: { id: '123', feature: 'premium' },
});

2. router.replace

Similar to push, but replaces the current history entry instead of adding a new one:

jsx
router.replace('/new-location');

3. router.back

Navigates to the previous page in history:

jsx
// Go back button
<button onClick={() => router.back()}>Go Back</button>

4. router.prefetch

Prefetch pages for faster client-side transitions:

jsx
// Prefetch a page when component mounts
useEffect(() => {
router.prefetch('/dashboard');
}, []);

Handling Navigation Events

Next.js router emits events you can listen to for additional control:

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

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

useEffect(() => {
// Handle route change start
const handleStart = (url) => {
console.log(`Loading: ${url}`);
// Show loading indicator
};

// Handle route change complete
const handleComplete = (url) => {
console.log(`Completed loading: ${url}`);
// Hide loading indicator
};

// Handle route change error
const handleError = (error) => {
console.error('Navigation error:', error);
// Hide loading indicator
};

router.events.on('routeChangeStart', handleStart);
router.events.on('routeChangeComplete', handleComplete);
router.events.on('routeChangeError', handleError);

// Clean up event listeners
return () => {
router.events.off('routeChangeStart', handleStart);
router.events.off('routeChangeComplete', handleComplete);
router.events.off('routeChangeError', handleError);
};
}, [router]);

return <div>Navigation Events Example</div>;
}

Scroll Restoration

By default, Next.js automatically handles scroll restoration for each page. When navigating back, it restores the scroll position. When navigating to a new page, it scrolls to the top.

You can customize this behavior with the scroll prop on the Link component:

jsx
// Don't scroll to top after navigation
<Link href="/long-page" scroll={false}>
Preserve scroll position
</Link>

Real-World Application: Building a Navigation Menu

Let's build a simple responsive navigation menu with active link highlighting:

jsx
// components/MainNavigation.js
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useState } from 'react';
import styles from './MainNavigation.module.css';

export default function MainNavigation() {
const router = useRouter();
const [isMenuOpen, setIsMenuOpen] = useState(false);

const isActive = (path) => {
return router.pathname === path;
};

const toggleMenu = () => {
setIsMenuOpen(prev => !prev);
};

return (
<nav className={styles.navigation}>
<div className={styles.logo}>
<Link href="/">My App</Link>
</div>

<button className={styles.menuButton} onClick={toggleMenu}>
Menu
</button>

<ul className={`${styles.navList} ${isMenuOpen ? styles.menuOpen : ''}`}>
<li>
<Link
href="/"
className={isActive('/') ? styles.active : ''}
>
Home
</Link>
</li>
<li>
<Link
href="/products"
className={isActive('/products') ? styles.active : ''}
>
Products
</Link>
</li>
<li>
<Link
href="/blog"
className={isActive('/blog') ? styles.active : ''}
>
Blog
</Link>
</li>
<li>
<Link
href="/contact"
className={isActive('/contact') ? styles.active : ''}
>
Contact
</Link>
</li>
</ul>
</nav>
);
}

And the corresponding CSS module:

css
/* MainNavigation.module.css */
.navigation {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.logo a {
font-size: 1.5rem;
font-weight: bold;
color: #333;
text-decoration: none;
}

.navList {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}

.navList li {
margin-left: 2rem;
}

.navList a {
color: #333;
text-decoration: none;
padding: 0.5rem 0;
}

.active {
font-weight: bold;
color: #0070f3 !important;
border-bottom: 2px solid #0070f3;
}

.menuButton {
display: none;
}

@media (max-width: 768px) {
.menuButton {
display: block;
background: none;
border: 1px solid #ddd;
padding: 0.5rem 1rem;
cursor: pointer;
}

.navList {
display: none;
position: absolute;
top: 70px;
left: 0;
right: 0;
background: #fff;
flex-direction: column;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.menuOpen {
display: flex;
}

.navList li {
margin: 0;
width: 100%;
text-align: center;
border-bottom: 1px solid #eee;
}

.navList a {
display: block;
padding: 1rem;
}
}

Advanced Navigation Patterns

Middleware for Protected Routes

You can use Next.js middleware for route protection:

jsx
// middleware.js
import { NextResponse } from 'next/server';

export function middleware(request) {
const { pathname } = request.nextUrl;

// Get authentication status
const isAuthenticated = request.cookies.get('authenticated');

// Protect dashboard routes
if (pathname.startsWith('/dashboard') && !isAuthenticated) {
return NextResponse.redirect(new URL('/login', request.url));
}

return NextResponse.next();
}

export const config = {
matcher: ['/dashboard/:path*'],
};

You might want to create your own Link component with consistent styling and behavior:

jsx
// components/CustomLink.js
import Link from 'next/link';
import { useRouter } from 'next/router';
import styles from './CustomLink.module.css';

export default function CustomLink({ href, exact = false, children, ...props }) {
const router = useRouter();
const isActive = exact
? router.pathname === href
: router.pathname.startsWith(href);

return (
<Link
href={href}
className={isActive ? styles.activeLink : styles.link}
{...props}
>
{children}
</Link>
);
}

Summary

In this tutorial, we've explored Next.js navigation features including:

  • Using the Link component for client-side navigation
  • Styling links and handling active states
  • Programmatic navigation with useRouter
  • Handling navigation events
  • Building responsive navigation menus
  • Advanced navigation patterns

Next.js provides powerful tools to create smooth, app-like experiences with client-side navigation. These features not only improve performance but also enhance user experience by reducing page load flicker and maintaining state between page transitions.

Additional Resources

Exercises

  1. Create a breadcrumb navigation component that automatically updates based on the current route.
  2. Implement a tabbed interface that uses client-side navigation.
  3. Build a mobile drawer navigation menu with slide-in animation.
  4. Create a pagination component that uses Next.js routing.
  5. Implement a navigation guard that asks for confirmation before leaving a form with unsaved changes.


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