Skip to content

Instantly share code, notes, and snippets.

@remy
Last active April 12, 2024 08:33
Show Gist options
  • Star 78 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save remy/0dde38897d6d660f0b63867c2344fb59 to your computer and use it in GitHub Desktop.
Save remy/0dde38897d6d660f0b63867c2344fb59 to your computer and use it in GitHub Desktop.
Next.js version of `activeClassName` support.
import Link from './Link'; // our version of link

export default () => (
  <header className="Header">
    <nav>
      <Link activeClassName="active" href="/">
        <a className="some-other-class">Home</a>
      </Link>
      <Link activeClassName="active" href="/about">
        <a>About</a>
      </Link>
      <Link activeClassName="active" href="/contact">
        <a>Contact</a>
      </Link>
    </nav>
  </header>
);
@shobhit-jain
Copy link

shobhit-jain commented Aug 16, 2020

I did something like this with typescript and tailwind css :

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

type Props = {
  href: string
  linkName: string
  activeClassName?: string
} & typeof defaultProps

const defaultProps = {
  activeClassName: 'text-green font-600',
}

export const NavLink = ({ href, linkName, activeClassName }: Props) => {
  const router = useRouter()

  return (
    <Link href={href}>
      <a className={router.pathname === href ? activeClassName : null}>
        {linkName}
      </a>
    </Link>
  )
}

NavLink.defaultProps = defaultProps

@nathansearles
Copy link

If you need support for dynamic routes ([slug], [id], [...param], [[...slug]], etc.) in Next.js. The following will check if the href contains a dynamic route and then check if the "router.asPath" is equal to the "props.as". When matched the "activeClassName" will be added to the link.

Thanks @remy!

import { useRouter } from "next/router";
import Link from "next/link";
import React, { Children } from "react";

const ActiveLink = ({ children, ...props }) => {
  const router = useRouter();
  const child = Children.only(children);
  let className = child.props.className || "";
  const isDynamicRoute = props.href.match(/^\/?\[{1,2}\.{0,3}[a-z]+\]{1,2}$/);

  if (
    router.pathname === props.href &&
    !isDynamicRoute &&
    props.activeClassName
  ) {
    className = `${className} ${props.activeClassName}`.trim();
  } else if (router.asPath === props.as && isDynamicRoute) {
    className = `${className} ${props.activeClassName}`.trim();
  }

  delete props.activeClassName;

  return <Link {...props}>{React.cloneElement(child, { className })}</Link>;
};

export default ActiveLink;

@TrejoCode
Copy link

A version with ES6 destructuring, proptypes and useRotuer hook:

import React from 'react';
import Link from 'next/link';
import PropTypes from 'prop-types';
import { useRouter } from 'next/router';

const ActiveLink = ({ href, activeClassName, children }) => {
  const router = useRouter();

  const child = React.Children.only(children);

  let className = child.props.className || '';
  if (router.pathname === href && activeClassName) {
    className = `${className} ${activeClassName}`.trim();
  }

  return <Link href={href}>{React.cloneElement(child, { className })}</Link>;
};

ActiveLink.propTypes = {
  href: PropTypes.string,
  activeClassName: PropTypes.string,
  children: PropTypes.node.isRequired
};
ActiveLink.defaultProps = {
  href: '',
  activeClassName: ''
};

export default ActiveLink;

Thanks a lot, for use:

<NavLink href = "/path" activeClassName = "--active">
    <a className="classes">
        Home Page
    </a>
</NavLink>

@SSylvain1989
Copy link

SSylvain1989 commented Mar 9, 2021

Hello , work for me .
find on SO https://stackoverflow.com/questions/53262263/target-active-link-when-the-route-is-active-in-next-js :

import Link from "next/link";
import { useRouter } from "next/router";


export const MyNav = () => {

  const router = useRouter();

  return (
    <ul>
      <li className={router.pathname == "/" ? "active" : ""}>
        <Link href="/">home</Link>
      </li>
      <li className={router.pathname == "/about" ? "active" : ""}>
        <Link href="/about">about</Link>
      </li>
    </ul>
  );
};

my code :

          <Link href="/">
            <a className={router.pathname ==="/" ? styles.navlinkActive : styles.navlink}>
              Accueil
            </a>
          </Link>

@GBrachetta
Copy link

GBrachetta commented Mar 24, 2021

All these look like great solutions, but regrettably I cannot make any to work reliably with this navbar structure:

import Navbar from 'react-bootstrap/Navbar';
import Nav from 'react-bootstrap/Nav';
import ActiveLink from '../CustomLink/ActiveLink';

export default function Navigation() {
  return (
    <Navbar collapseOnSelect expand="lg" bg="dark" variant="dark">
      <ActiveLink activeClassName="active" href="/">
        <Navbar.Brand>
          <img src="/logo.svg" alt="WhiteQueen Logo" height={50} />
        </Navbar.Brand>
      </ActiveLink>
      <Navbar.Toggle aria-controls="responsive-navbar-nav" />
      <Navbar.Collapse id="responsive-navbar-nav">
        <Nav className="mr-auto">
          <ActiveLink activeClassName="active" href="/">
            <Nav.Link>Home</Nav.Link>
          </ActiveLink>
          <ActiveLink activeClassName="active" href="/publications">
            <Nav.Link>Publications</Nav.Link>
          </ActiveLink>
          <ActiveLink activeClassName="active" href="/links">
            <Nav.Link>Links</Nav.Link>
          </ActiveLink>
        </Nav>
        <Nav>
          <ActiveLink activeClassName="active" href="/contact">
            <Nav.Link>Contact</Nav.Link>
          </ActiveLink>
        </Nav>
      </Navbar.Collapse>
    </Navbar>
  );
}

If I click on a link nested in a different <Nav> (for example, "Contact") the highlighting of the previous one isn't removed...

Am I missing something obvious?

@itsjavi
Copy link

itsjavi commented Apr 21, 2021

I rather have my own function that creates a pure next/link component:

import Link from "next/link";
import {useRouter} from "next/router";
import styles from './Toolbar.module.css'

const Toolbar = () => {
    const router = useRouter();
    const activeLink = (path, content, activeClass = styles.active, normalClass = '') => {
        let className = router.pathname === path ? activeClass : normalClass;
        return <Link href={path}><a className={className}>{content}</a></Link>
    }

    return <div className={styles.toolbar}>
        <nav>
            {activeLink('/', 'Home')}
            {activeLink('/about', 'About')}
        </nav>
    </div>
}

export default Toolbar;

If you need a more reusable function, you can pass the router:

import Link from "next/link"

const createActiveLink = (router, path, content, activeClass = 'active', normalClass = '') => {
      let className = router.pathname === path ? activeClass : normalClass;
      return <Link href={path}><a className={className}>{content}</a></Link>
  }

export default createActiveLink

@su-pull
Copy link

su-pull commented Oct 30, 2021

typed. 21/10/22
Thank you very much remy san XD

import { withRouter, NextRouter  } from 'next/router';
import React, { ReactElement } from 'react';
import Link from 'next/link';

type Props = {
  router: NextRouter;
  children: ReactElement;
  href: string;
  activeClassName: string;
}

const ActiveLink = ({ router, children, ...props }: Props) => {
  const child = children;
  
  let className: string = child.props.className;
  if (router.pathname == props.href) {
    className = `${className} ${props.activeClassName}`;
  }

  return (
  <Link {...props}>{React.cloneElement(child, { className })}</Link>
  );
}

export default withRouter(ActiveLink);

@mr9d
Copy link

mr9d commented Jan 16, 2022

Here's my version of ActiveLink for Next.js + TypeScript:

import Link, { LinkProps } from "next/link";
import { NextRouter, useRouter } from "next/router";
import { FC, PropsWithChildren } from "react";

type ActiveLinkProps = LinkProps & {
  activeClassName: string;
  className: string;
};

export const ActiveLink: FC<ActiveLinkProps> = ({ children, ...props }: PropsWithChildren<ActiveLinkProps>) => {
  const router: NextRouter = useRouter();
  const className: string =
    props.className + ((router.pathname === props.href && props.activeClassName) ? " " + props.activeClassName : "");
  return (
    <Link {...props}>
      <a className={className}>{children}</a>
    </Link>
  );
};

Example of usage:

          <ActiveLink href="/" className="menulink" activeClassName="active">
            Home
          </ActiveLink>

The difference is you no longer need <a> tag inside.

Benefits:

  • TypeScript-friendly
  • You no longer need to find child element and its type with Children.only
  • You no longer need to do React.cloneElement
  • You won't forget to put <a> inside anymore.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment