Simplifying Active Navigation in Next.js with a Context-Based RouterLink
Introduction
Building user-friendly and accessible navigation is a cornerstone of modern web development. In applications using frameworks like Next.js, it's crucial to indicate which navigation link corresponds to the current page. This ensures a clear and intuitive experience for users while improving accessibility for assistive technologies and search engine optimization (SEO). In this post, we'll explore how to create a reusable, scalable RouterLink component in Next.js, leveraging React Context to handle active link states effectively.
Why You Need Active Navigation Indication
Active link indication serves two critical purposes:
- User Experience: It provides visual feedback, such as highlighting the active link, to guide users within your application.
- Accessibility (WCAG Compliance): According to the Web Content Accessibility Guidelines (WCAG) Success Criterion 2.4.7, it is essential to provide clear indications of the user's current location within a website or application. Using the
aria-currentattribute is a recommended practice to meet this criterion, enhancing accessibility for users relying on screen readers. - Search Engine Optimization (SEO): Clear and structured navigation improves crawlability, making it easier for search engines to index your site and understand its structure, boosting your site's SEO performance.
While you can manually manage active states using tools like useRouter or usePathname, this approach can lead to repetitive code, especially in large applications with multiple navigation links. A context-based solution not only removes redundancy but also ensures cleaner, more maintainable code.
Challenges with Manual Active State Management
In Next.js, developers often rely on the useRouter or usePathname hooks to determine the current route. For instance:
1import Link from "next/link"2import { useRouter } from "next/router"34export const RouterLink = ({5href,6children,7}: {8href: string9children: React.ReactNode10}) => {11const { asPath } = useRouter()1213const isActive = asPath === href14const ariaCurrent = isActive ? "page" : undefined1516return (17<Link href={href} aria-current={ariaCurrent}>18{children}19</Link>20)21}
While functional, this approach becomes cumbersome when:
- Repeatedly comparing
asPathorusePathnamein every link component. - Passing props like
currentPathmanually across deeply nested components.
By introducing a RouterLinkProvider with React Context, you can streamline this process and centralize route management.
The Context-Based Solution
The goal is to create a RouterLinkProvider that automatically provides the currentPath (using usePathname) to all RouterLink components. This eliminates the need for redundant logic and manual prop passing.
Step 1: Setting Up the Context
Create a RouterLinkContext using React's createContext API:
1"use client"23import { createContext, ReactNode, useContext } from "react"4import { usePathname } from "next/navigation"56const RouterLinkContext = createContext<string | undefined>(undefined)78export const RouterLinkProvider = ({ children }: { children: ReactNode }) => {9const pathname = usePathname() // Automatically gets the current path1011return (12<RouterLinkContext.Provider value={pathname}>13{children}14</RouterLinkContext.Provider>15)16}1718export const useRouterLinkContext = () => {19const context = useContext(RouterLinkContext)20if (!context) {21throw new Error(22"useRouterLinkContext must be used within a RouterLinkProvider"23)24}25return context26}
This provider ensures that the pathname is accessible to all components within the context.
Step 2: Updating the RouterLink Component
Refactor the RouterLink component to consume the currentPath from the context:
1import { AnchorHTMLAttributes, ReactNode } from "react"2import Link, { LinkProps } from "next/link"34import { useRouterLinkContext } from "./RouterLinkContext"56type RouterLinkProps = LinkProps &7AnchorHTMLAttributes<HTMLAnchorElement> & {8children: ReactNode9}1011export const RouterLink = ({ href, children, ...props }: RouterLinkProps) => {12const currentPath = useRouterLinkContext() // Access current path from context1314const isActive = currentPath === href15const ariaCurrent = isActive ? "page" : undefined1617return (18<Link {...props} href={href} aria-current={ariaCurrent}>19{children}20</Link>21)22}
This component dynamically calculates the active state and adds the appropriate aria-current attribute.
Step 3: Integrating the Provider
Wrap your application with the RouterLinkProvider in your root layout or a higher-level component:
1import { RouterLinkProvider } from "./RouterLinkContext"23export default function RootLayout({4children,5}: {6children: React.ReactNode7}) {8return (9<html>10<body>11<RouterLinkProvider>{children}</RouterLinkProvider>12</body>13</html>14)15}
Step 4: Using RouterLink
Now you can use the RouterLink component without worrying about manually managing the active state:
1import { RouterLink } from "./RouterLink"23export default function Navigation() {4return (5<nav>6<RouterLink href="/about" className="text-gray-500">7About Us8</RouterLink>9<RouterLink href="/contact" className="text-gray-500">10Contact Us11</RouterLink>12</nav>13)14}
Benefits of the Context-Based Approach
- Centralized State Management: The
RouterLinkProviderhandles the current path globally, reducing redundancy. - Improved Readability: No need to pass
currentPathmanually to eachRouterLink. - Scalability: Works seamlessly in large applications with deeply nested navigation components.
- Accessibility: Automatically adds
aria-currentfor active links, meeting WCAG Success Criterion 2.4.7. - Enhanced SEO: Clear navigation with
aria-currenthelps search engines understand the site's structure, improving indexation and ranking.
Conclusion
Building accessible and intuitive navigation doesn't have to be repetitive or cumbersome. By leveraging React Context and Next.js's usePathname, you can create a scalable and reusable RouterLink system that simplifies your codebase and improves the user experience. This approach is particularly beneficial for large applications where manual active state management can quickly become unmanageable.
With the RouterLinkProvider in place, your navigation logic is centralized, your components are cleaner, and your users—both human and machine—will thank you for the seamless experience. Start implementing it today and make your Next.js apps more robust, accessible, and SEO-friendly!