In modern web applications, navigation is a critical aspect of providing an excellent user experience. One vital element of navigation is highlighting the active link, which gives users a clear indication of their current position within the application. Next.js 14 introduces the App router, which includes the usePathname
hook for reading the current URL’s pathname.
In this article, we will walk through setting up a sample Navigation component and create a helper function to check the active navigation link using the usePathname
hook. We will also cover handling nested URLs and the root path.
Setting up a sample Navigation component using next/link
Before diving into creating the helper function to check the active navigation link, let’s set up a simple Navigation component with a navigation structure. First, define the navigation array, which includes the href
, an icon, and a name for each navigation item:
type NavigationItem = {
href: string
icon: React.ComponentType
name: string
}
const navigation: NavigationItem[] = [
{ href: '/', icon: HomeIcon, name: 'Dashboard' },
{ href: '/feedback', icon: UsersIcon, name: 'Feedback' },
{ href: '/roadmap', icon: FolderIcon, name: 'Roadmap' },
{ href: '/comments', icon: CalendarIcon, name: 'Comments' },
]
Now, let’s create a simple Navigation component that renders the navigation items in a list:
import Link from 'next/link'
export function Navigation() {
return (
<nav>
<ul>
{navigation.map(({ href, icon: Icon, name }) => (
<li key={href}>
<Link href={href}>
<Icon />
<span>{name}</span>
</Link>
</li>
))}
</ul>
</nav>
)
}
Creating a basic useActivePath helper function with usePathname hook
The critical piece of our navigation is identifying the active link. We will use a helper function that will take the href
of each navigation item and determine if it’s active based on the current pathname.
Next.js 14 introduced the usePathname
hook within the next/navigation
package. Import the hook and use it to access the current pathname:
import { usePathname } from 'next/navigation'
Now that we have the pathname, let’s create a helper function that evaluates if the current URL matches the navigation item’s href
and eventually highlights the active navigation link.
import { usePathname } from 'next/navigation'
export function useActivePath(): (path: string) => boolean {
const pathname = usePathname()
const checkActivePath = (path: string) => {
return path === pathname
}
return checkActivePath
}
The checkActivePath
function compares the path argument to the current pathname
. Next, update the Navigation component to use this helper and conditionally apply the “active” className for the current navigation link.
"use client";
import Link from 'next/link'
import { useActivePath } from './helper'
export function Navigation() {
const checkActivePath = useActivePath()
return (
<nav>
<ul>
{navigation.map(({ href, icon: Icon, name }) => (
<li key={href}>
<Link href={href} className={checkActivePath(href) ? 'active' : ''}>
<Icon />
<span>{name}</span>
</Link>
</li>
))}
</ul>
</nav>
)
}
Handling nested paths and the root path issue
The initial implementation of our checkActivePath
function works well for simple URLs. However, when we navigate to nested paths like /feedback/id
, the Feedback navigation link isn’t active. To handle such cases, update the checkActivePath
function to match paths that start with the same URL segments:
import { usePathname } from 'next/navigation'
export function useActivePath(): (path: string) => boolean {
const pathname = usePathname()
const checkActivePath = (path: string) => {
return pathname.startsWith(path)
}
return checkActivePath
}
Now, the checkActivePath
function will return true
for /feedback
when the URL is /feedback/id
.
However, with this implementation, when the URL is /feedback/id
, both the root (/
) and the Feedback links receive the active
class. We want to ensure only the Feedback link is active in this case. To fix this, let’s add a special case for the root path (/
) in the checkActivePath
function:
import { usePathname } from 'next/navigation'
export function useActivePath(): (path: string) => boolean {
const pathname = usePathname()
const checkActivePath = (path: string) => {
if (path === '/' && pathname !== path) {
return false
}
return pathname.startsWith(path)
}
return checkActivePath
}
Now, the root navigation item will only receive the active
class when the URL is exactly equal to the root (/
), ensuring the correct behavior of our Navigation component.
Final solution
Based on the changes discussed above, our final solution to check the active navigation link in Next.js 14 using the App router and the usePathname
hook is:
// app/helper.ts
import { usePathname } from 'next/navigation'
export function useActivePath(): (path: string) => boolean {
const pathname = usePathname()
const checkActivePath = (path: string) => {
if (path === '/' && pathname !== path) {
return false
}
return pathname.startsWith(path)
}
return checkActivePath
}
To use this hook inside a component, update the Navigation component as follows:
'use client'
import Link from 'next/link'
import { useActivePath } from './helper'
export function Navigation() {
const checkActivePath = useActivePath()
return (
<nav>
<ul>
{navigation.map(({ href, icon: Icon, name }) => (
<li key={href}>
<Link href={href} className={checkActivePath(href) ? 'active' : ''}>
<Icon />
<span>{name}</span>
</Link>
</li>
))}
</ul>
</nav>
)
}
This helper function is now capable of handling the active navigation link for both simple and nested URLs while addressing the root path issue. By using the useActivePath
hook in the Navigation component, developers can create a dynamic navigation experience that highlights the active link correctly.
In conclusion, using Next.js 14’s App router and the usePathname
hook, checking the active navigation link can greatly enhance the user experience in your Next.js applications. By following the above implementation, developers can create a helper function to identify the active navigation link and ensure correct navigation context in their projects.