diff --git a/.changeset/unlucky-hats-play.md b/.changeset/unlucky-hats-play.md
new file mode 100644
index 000000000..7ccf2de7c
--- /dev/null
+++ b/.changeset/unlucky-hats-play.md
@@ -0,0 +1,5 @@
+---
+"react-router-dom": patch
+---
+
+Fix NavLink behavior for root urls
diff --git a/packages/react-router-dom/__tests__/nav-link-active-test.tsx b/packages/react-router-dom/__tests__/nav-link-active-test.tsx
index 8b9da3c44..4e00e30fe 100644
--- a/packages/react-router-dom/__tests__/nav-link-active-test.tsx
+++ b/packages/react-router-dom/__tests__/nav-link-active-test.tsx
@@ -288,6 +288,57 @@ describe("NavLink", () => {
expect(anchors.map((a) => a.props.className)).toEqual(["active", ""]);
});
+
+ it("does not automatically apply to root non-layout segments", () => {
+ let renderer: TestRenderer.ReactTestRenderer;
+ TestRenderer.act(() => {
+ renderer = TestRenderer.create(
+
+
+ Root} />
+ Root}
+ >
+
+
+ );
+ });
+
+ let anchor = renderer.root.findByType("a");
+
+ expect(anchor.props.className).not.toMatch("active");
+ });
+
+ it("does not automatically apply to root layout segments", () => {
+ let renderer: TestRenderer.ReactTestRenderer;
+ TestRenderer.act(() => {
+ renderer = TestRenderer.create(
+
+
+
+ Root
+
+ >
+ }
+ >
+ Root}
+ >
+
+
+
+ );
+ });
+
+ let anchor = renderer.root.findByType("a");
+
+ expect(anchor.props.className).not.toMatch("active");
+ });
});
describe("when it matches just the beginning but not to the end", () => {
diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx
index 9c1e46fda..6cd01dc86 100644
--- a/packages/react-router-dom/index.tsx
+++ b/packages/react-router-dom/index.tsx
@@ -442,24 +442,36 @@ export const NavLink = React.forwardRef(
ref
) {
let path = useResolvedPath(to, { relative: rest.relative });
- let match = useMatch({ path: path.pathname, end, caseSensitive });
-
+ let location = useLocation();
let routerState = React.useContext(DataRouterStateContext);
- let nextLocation = routerState?.navigation.location;
- let nextPath = useResolvedPath(nextLocation || "");
- let nextMatch = React.useMemo(
- () =>
- nextLocation
- ? matchPath(
- { path: path.pathname, end, caseSensitive },
- nextPath.pathname
- )
- : null,
- [nextLocation, path.pathname, caseSensitive, end, nextPath.pathname]
- );
- let isPending = nextMatch != null;
- let isActive = match != null;
+ let toPathname = path.pathname;
+ let locationPathname = location.pathname;
+ let nextLocationPathname =
+ routerState && routerState.navigation && routerState.navigation.location
+ ? routerState.navigation.location.pathname
+ : null;
+
+ if (!caseSensitive) {
+ locationPathname = locationPathname.toLowerCase();
+ nextLocationPathname = nextLocationPathname
+ ? nextLocationPathname.toLowerCase()
+ : null;
+ toPathname = toPathname.toLowerCase();
+ }
+
+ let isActive =
+ locationPathname === toPathname ||
+ (!end &&
+ locationPathname.startsWith(toPathname) &&
+ locationPathname.charAt(toPathname.length) === "/");
+
+ let isPending =
+ nextLocationPathname != null &&
+ (nextLocationPathname === toPathname ||
+ (!end &&
+ nextLocationPathname.startsWith(toPathname) &&
+ nextLocationPathname.charAt(toPathname.length) === "/"));
let ariaCurrent = isActive ? ariaCurrentProp : undefined;