Add next/nav-link for active-aware navigation links#95922
Draft
aurorascharff wants to merge 9 commits into
Draft
Add next/nav-link for active-aware navigation links#95922aurorascharff wants to merge 9 commits into
aurorascharff wants to merge 9 commits into
Conversation
Contributor
Tests PassedCommit: 2127bb2 |
Contributor
Stats from current PR🔴 1 regression, 3 improvements
📊 All Metrics📖 Metrics GlossaryDev Server Metrics:
Build Metrics:
Change Thresholds:
⚡ Dev Server
📦 Dev Server (Webpack) (Legacy)📦 Dev Server (Webpack)
⚡ Production Builds
📦 Production Builds (Webpack) (Legacy)📦 Production Builds (Webpack)
📦 Bundle SizesBundle Sizes⚡ TurbopackClient Main Bundles
Server Middleware
Build DetailsBuild Manifests
📦 WebpackClient Main Bundles
Polyfills
Pages
Server Edge SSR
Middleware
Build DetailsBuild Manifests
Build Cache
🔄 Shared (bundler-independent)Runtimes
📝 Changed Files (2 files)Files with changes:
View diffspages-api-tu..ntime.dev.jsDiff too large to display pages-turbo...ntime.dev.jsDiff too large to display 📎 Tarball URLCommit: 2127bb2 |
next/nav-link for active-aware navigation links
next/nav-link for active-aware navigation links
aurorascharff
marked this pull request as ready for review
July 18, 2026 15:26
aurorascharff
marked this pull request as draft
July 18, 2026 15:26
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a first-party next/nav-link entrypoint (App Router–only) that extends Link with server-resolved active state (aria-current="page") plus render-prop support for { isActive, isPending }, and documents/tests the new API.
Changes:
- Introduces
NavLinkimplementations for App Router (client + react-server guard) and a Pages Router stub that throws. - Extends the App Router
Linkimplementation to allowNavLinkto resolve functionclassName/childreninside the anchor (to access pending state). - Adds docs references + a new
NavLinkdocs page, and adds an e2e fixture/test suite (includingcacheComponentscoverage).
Reviewed changes
Copilot reviewed 26 out of 27 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/e2e/app-dir/navlink/next.config.js | Enables cacheComponents for the NavLink e2e fixture. |
| test/e2e/app-dir/navlink/navlink.test.ts | E2E coverage for SSR active state, prefix vs exact matching, function props, and client navigation updates. |
| test/e2e/app-dir/navlink/app/page.tsx | Fixture home page. |
| test/e2e/app-dir/navlink/app/nav.tsx | Fixture nav rendering multiple NavLink variants. |
| test/e2e/app-dir/navlink/app/layout.tsx | Fixture root layout that renders the nav for all pages. |
| test/e2e/app-dir/navlink/app/contact/page.tsx | Fixture contact route. |
| test/e2e/app-dir/navlink/app/blog/page.tsx | Fixture blog index route. |
| test/e2e/app-dir/navlink/app/blog/[slug]/page.tsx | Fixture nested blog route for prefix-matching coverage. |
| test/e2e/app-dir/navlink/app/about/page.tsx | Fixture about route. |
| packages/next/src/client/nav-link.tsx | Pages Router entry (type surface + runtime error). |
| packages/next/src/client/app-dir/nav-link.tsx | App Router client NavLink implementation (active detection + aria-current). |
| packages/next/src/client/app-dir/nav-link.react-server.tsx | RSC boundary guard with a NavLink-specific error for function props in Server Components. |
| packages/next/src/client/app-dir/link.tsx | Internal Link changes to resolve render-prop className/children for NavLink (and provide isPending). |
| packages/next/src/build/create-compiler-aliases.ts | Adds webpack aliasing for next/nav-link across conditions (including react-server). |
| packages/next/src/api/nav-link.ts | Adds the next/dist/api/nav-link entrypoint for import maps/aliases. |
| packages/next/package.json | Ships nav-link.js and nav-link.d.ts in the published package files list. |
| packages/next/nav-link.js | Adds the CJS subpath entry for next/nav-link. |
| packages/next/nav-link.d.ts | Adds types entry for next/nav-link. |
| packages/next/errors.json | Adds error messages for pages-router usage and RSC function-prop misuse. |
| docs/01-app/03-api-reference/04-functions/use-selected-layout-segments.mdx | Adds related links + guidance pointing to NavLink for link-level active styling. |
| docs/01-app/03-api-reference/04-functions/use-selected-layout-segment.mdx | Adds related links + guidance referencing NavLink. |
| docs/01-app/03-api-reference/04-functions/use-pathname.mdx | Adds related links + guidance recommending NavLink for active link styling. |
| docs/01-app/03-api-reference/04-functions/use-link-status.mdx | Adds NavLink as a related page. |
| docs/01-app/03-api-reference/02-components/nav-link.mdx | New docs page for NavLink API/behavior. |
| docs/01-app/03-api-reference/02-components/link.mdx | Updates “Checking active links” to prefer NavLink and explain benefits. |
| docs/01-app/01-getting-started/04-linking-and-navigating.mdx | Adds NavLink to related links. |
| crates/next-core/src/next_import_map.rs | Adds Turbopack import map aliases for next/nav-link (client/server/edge + RSC). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Contributor
|
great! two questions:
|
Contributor
Author
Great questions!
Committed the changes here: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
Adds a
NavLinkcomponent, similar to React Router'sNavLink, to simplify a common task.Why?
There's a lot of gotchas when doing this yourself, which I have documented here, so it would be great if Next.js could have this built in. https://aurorascharff.no/posts/building-an-active-navlink-component-in-nextjs/
How?
<NavLink>for the App Router that knows when it points at the current route. It setsdata-activeon the active link — including an ancestor matched by prefix — for styling, andaria-current="page"only on the exact page, so an ancestor is never announced as the current page.data-active:Tailwind variant (or a[data-active]rule), or take full control by passingclassName/childrenas a function of{ isActive, isPending }./blogis active on/blog/post); addexactfor an exact-only match. Matching uses the pathname; for query-string state such as tabs, readuseSearchParams()yourself.cacheComponents— no extraSuspense.NavLink-specific error if you use the function form from a Server Component.A `next/nav-link` entry that reuses `Link`, reads the pathname untracked, and resolves the render props inside the anchor.
Its own module.
next/nav-linkis a separate entry (likenext/form) rather than an overload ofnext/link. Webpack aliases (create-compiler-aliases.ts) and the Turbopack import map (next_import_map.rs) mirrornext/linkacross the base / app-client / react-server conditions; the react-server variant throws the Server-Component error, and the Pages Router entry throws "App Router only".Two things a userland wrapper can't do:
cacheComponents. The active read usesuseUntrackedPathname(), which readsPathnameContextdirectly and skips the dynamic-API guard that makes a hand-writtenusePathname()nav hang on fallback-param prerenders. The context is SSR-seeded, so the active class is already in the initial HTML for static routes.isPendingreachesclassName, not justchildren. Both function props are resolved inside the anchor render, where the optimistic link status lives. A userland wrapper setsclassNameonLinkfrom the outside, so it can only observe pending viauseLinkStatusinsidechildren— never inclassName.Two attributes, two meanings.
data-activeis present on any match (exact or prefix) for styling;aria-current="page"is set only on an exact match, so a prefix-matched ancestor getsdata-activebut notaria-current.Matching.
exact→pathname === href; default →pathname === href || pathname.startsWith(href + '/'), with/exact. Trailing slashes are normalized so matching is stable undertrailingSlash.