better-link
v0.2.0
Published
Enhanced Next.js Link component with isActive functionality
Maintainers
Readme
Better Link
✨ Features
- Active State Detection - Automatically determines if a link is active based on the current URL
- Multiple APIs - Choose between different patterns based on your preferences
- Fully Typed - Complete TypeScript support with proper typing
- Zero Dependencies - Only peer dependencies on Next.js and React
- Small Footprint - Lightweight implementation with minimal overhead
- Flexible Patterns - Support for both class-based and render props patterns
📦 Installation
# Using npm
npm install better-link
# Using yarn
yarn add better-link
# Using pnpm
pnpm add better-link
# Using bun
bun add better-link🚀 Quick Start
For Next.js App Router (v13+)
If you're using Next.js with App Router, use the special factory pattern:
"use client"; // IMPORTANT: This must be a client component
import { createNavLink } from "better-link";
// Create the NavLink component inside your client component
const NavLink = createNavLink();
export function Navigation() {
return (
<nav>
<NavLink href="/" exact activeClassName="active">
Home
</NavLink>
<NavLink href="/about" activeClassName="active">
About
</NavLink>
</nav>
);
}Then import your Navigation component in your layout:
// layout.jsx (server component)
import { Navigation } from "./navigation";
export default function Layout({ children }) {
return (
<div>
<Navigation />
<main>{children}</main>
</div>
);
}For Other React Applications
import { NavLink } from "better-link";
function Navigation() {
return (
<nav>
<NavLink href="/" exact activeClassName="active">
Home
</NavLink>
<NavLink href="/about" activeClassName="active">
About
</NavLink>
</nav>
);
}📚 Usage
Important: Client Components in Next.js
Since better-link uses the usePathname hook from Next.js, make sure to use it in client components:
// navigation.tsx
"use client"; // Add this directive
import { createNavLink } from "better-link";
// Create the component inside your client component
const NavLink = createNavLink();
export function Navigation() {
return (
<nav>
<NavLink href="/" exact activeClassName="active">
Home
</NavLink>
<NavLink href="/about" activeClassName="active">
About
</NavLink>
</nav>
);
}Then import your Navigation component in your layout:
// layout.tsx
import { Navigation } from "./navigation";
export default function Layout({ children }) {
return (
<div>
<Navigation />
<main>{children}</main>
</div>
);
}Using the Factory Pattern for Advanced Links
"use client";
import { createAdvancedNavLink } from "better-link";
// Create the component inside your client component
const AdvancedNavLink = createAdvancedNavLink();
export function SideNav() {
return (
<nav>
<AdvancedNavLink to="/dashboard">
{({ isActive }) => (
<span className={isActive ? "text-blue-500" : "text-gray-500"}>
Dashboard
</span>
)}
</AdvancedNavLink>
</nav>
);
}Basic NavLink
The NavLink component extends Next.js's Link component with active state detection:
import { NavLink } from "better-link";
export default function Navigation() {
return (
<nav className="flex gap-4">
<NavLink
href="/"
exact
className="text-gray-500"
activeClassName="font-bold text-blue-600"
>
Home
</NavLink>
<NavLink
href="/about"
className="text-gray-500"
activeClassName="font-bold text-blue-600"
>
About
</NavLink>
</nav>
);
}Render Props Pattern
For more flexibility, use renderChildren to customize your link based on its active state:
import { NavLink } from "better-link";
export default function Navigation() {
return (
<nav className="flex gap-4">
<NavLink
href="/dashboard"
renderChildren={(isActive) => (
<div className="flex items-center gap-2">
<span
className={isActive ? "text-blue-600 font-bold" : "text-gray-500"}
>
Dashboard
</span>
{isActive && (
<span className="px-2 py-1 bg-blue-100 text-xs rounded-full">
Active
</span>
)}
</div>
)}
>
Dashboard
</NavLink>
</nav>
);
}Advanced Pattern
For even more control, use the AdvancedNavLink component with the render props pattern:
import { AdvancedNavLink } from "better-link";
export default function Navigation() {
const menuItems = [
{ id: "home", label: "Home", href: "/" },
{ id: "dashboard", label: "Dashboard", href: "/dashboard" },
{ id: "settings", label: "Settings", href: "/settings" },
];
return (
<nav className="flex flex-col gap-2">
{menuItems.map((item) => (
<AdvancedNavLink to={item.href} key={item.id}>
{({ isActive }) => (
<div
className={`
p-3 rounded-lg transition-all
${
isActive
? "bg-blue-50 text-blue-600 border-l-4 border-blue-600"
: "bg-white text-gray-600 hover:bg-gray-50"
}
`}
>
{item.label}
{isActive && <span className="ml-2">•</span>}
</div>
)}
</AdvancedNavLink>
))}
</nav>
);
}🔧 API Reference
NavLink
NavLink extends Next.js's Link component with active state functionality.
Props
| Prop | Type | Default | Description |
| ----------------- | ---------------------------------------- | -------- | ------------------------------------------------------------------- |
| href | string | Required | URL to navigate to |
| children | React.ReactNode | Required | Link content |
| className | string | '' | Base class name for the link |
| activeClassName | string | '' | Class name to apply when the link is active |
| exact | boolean | false | If true, the link will only be active when the path matches exactly |
| renderChildren | (isActive: boolean) => React.ReactNode | - | Function to render children with isActive state |
All other props from Next.js's Link component are also supported.
AdvancedNavLink
AdvancedNavLink provides a more flexible API using render props pattern.
Props
| Prop | Type | Default | Description |
| ---------- | ------------------------------------------------------------------------ | -------- | ------------------------------------------ |
| to | string | Required | URL to navigate to |
| key | string \| number | - | Optional key for the link |
| children | React.ReactNode \| ((props: { isActive: boolean }) => React.ReactNode) | Required | Either static content or a render function |
All other props from Next.js's Link component are also supported.
🔍 How It Works
better-link uses Next.js's usePathname hook to determine if a link is active. It compares the current pathname with the link's href or to property:
// For exact matching
const isActive = pathname === href;
// For partial matching (default)
const isActive = pathname.startsWith(href);🧰 Use Cases
- Navigation Menus - Highlight the current section in your app
- Breadcrumbs - Show the active path in breadcrumb navigation
- Tab Interfaces - Build tab interfaces with active indicators
- Sidebar Navigation - Create sidebar menus with active states
- Pagination - Highlight the current page in pagination controls
🌱 Upcoming Features
- [ ] Support for pattern matching URLs
- [ ] Active state based on query parameters
- [ ] Animation helpers for transitions
- [ ] Additional accessibility improvements
🤝 Contributing
Contributions are always welcome! Feel free to open issues or submit pull requests.
- Fork the repository
- Create your feature branch:
git checkout -b feature/amazing-feature - Commit your changes:
git commit -m 'Add some amazing feature' - Push to the branch:
git push origin feature/amazing-feature - Open a pull request
📄 License
Distributed under the MIT License. See LICENSE for more information.
📌 Built With
❓ Troubleshooting
"Invalid Hook Call" Error in Next.js App Router
If you encounter an error like this:
Invalid hook call. Hooks can only be called inside of the body of a function component.Make sure you are using the factory pattern for Next.js App Router:
"use client"; // IMPORTANT!
import { createNavLink } from "better-link";
// Create the component inside your client component
const NavLink = createNavLink();
export function Navigation() {
return (
<nav>
<NavLink href="/" exact activeClassName="active">
Home
</NavLink>
</nav>
);
}This factory pattern ensures hooks are used correctly and avoids the invalid hook call error.
🌱 Upcoming Features
- [ ] Support for pattern matching URLs
- [ ] Active state based on query parameters
- [ ] Animation helpers for transitions
- [ ] Additional accessibility improvements
