skip to content

In React Router v7, how does a relative to like ".." resolve inside nested routes, and what does relative="path" change?

level: middleimportance: must knowfreq 57%

answer

  1. relative to the route, not the URL
  2. the route that renders the link
  3. .. climbs one route level
  4. relative="path" climbs one URL segment

basics

~20 s

Relative to values resolve against the route hierarchy where the link is rendered, so ".." moves up one route and drops that route's whole path pattern. relative="path" makes ".." drop a single URL segment instead.

solid answer

~40 s

A `to` without a leading slash is resolved against the **route that renders the link**, not against the browser URL. `".."` means "go up one route": with `profile/edit` declared as one child of `settings`, a Back link `<Link to="..">` in that child goes to `/settings`, removing both segments of the child's pattern. Pathless layout routes and index routes add nothing to the path, so they are skipped when counting. `relative="path"` switches to URL semantics: `".."` removes exactly one segment, giving `/settings/profile`. A plain relative name like `to="billing"` rendered in the settings layout always means `/settings/billing`, whichever child is showing. `to` values that start with `/` are absolute from the router's basename.

code

tsx · 16 lines
tsx
import { Link } from "react-router";

// Rendered by the route { path: "profile/edit" }, a child of "settings".
export function EditProfile() {
  return (
    <header>
      {/* /settings/profile/edit -> /settings (one route up) */}
      <Link to="..">All settings</Link>

      {/* /settings/profile/edit -> /settings/profile (one segment up) */}
      <Link to=".." relative="path">
        Back to profile
      </Link>
    </header>
  );
}

go deeper

for a junior

Recall that to without a leading slash is relative to the route rendering the link, and that ".." goes up one route by default.

for a middle

Walk through a multi-segment child path, show where ".." lands with and without relative="path", and explain why pathless and index routes are skipped.

for a senior

Diagnose a Back link that jumps two levels, choose between restructuring nested routes and relative="path", and keep feature links portable across mount points.

for a principal

Set a convention for large apps: relative links inside a feature, absolute links across features, and a route hierarchy that mirrors the URL so ".." stays predictable.

## Two ways to read `".."` In a browser, `<a href="..">` is resolved against the **current URL**: drop the last segment, and that is the new location. React Router's `to` deliberately works differently. By default (`relative="route"`), a relative `to` is resolved against the **route hierarchy**, starting from the route whose element rendered the link, and each leading `..` moves up **one route**, not one URL segment. The React Router source calls this out as a major reason the prop is named `to` and not `href`. ## A nested settings area Consider this route tree: ```tsx createBrowserRouter([ { path: "settings", element: <SettingsLayout />, children: [ { index: true, element: <SettingsHome /> }, { path: "profile/edit", element: <EditProfile /> }, { path: "billing", element: <Billing /> }, ], }, ]); ``` At `/settings/profile/edit`: | Link rendered in `EditProfile` | Resolves to | Why | |---|---|---| | `to=".."` | `/settings` | one route up; the child's pattern `profile/edit` is dropped as a unit | | `to=".." relative="path"` | `/settings/profile` | one URL segment up | | `to="../billing"` | `/settings/billing` | up to `settings`, then down to `billing` | | `to="/settings/billing"` | `/settings/billing` | absolute, ignores the hierarchy | And a link rendered in `SettingsLayout` itself, such as `to="billing"`, resolves to `/settings/billing` **regardless of which child is active**, because it is relative to the settings route, not to the leaf URL. With `<a href="billing">` at `/settings/profile/edit` the browser would produce `/settings/profile/billing`. ## Which routes count When climbing, React Router only counts routes that **contribute a path**: - **Layout routes without a `path`** add nesting but no URL segments, so `..` passes straight through them. - **Index routes** have no path of their own; they share their parent's URL. - A route whose path has several segments (`profile/edit`, `:id/edit`) is still **one** route, so `..` removes all of its segments together. That last rule is the usual surprise. Declaring `profile/edit` as one route and adding a Back link with `to=".."` sends the user to `/settings`, skipping `/settings/profile`. There are two fixes: 1. nest the routes as `profile` → `edit`, so the route hierarchy matches the URL hierarchy; or 2. keep the flat declaration and write `relative="path"` on that link. ## The same rules everywhere The resolution logic is shared, so the same options apply to `<Link>`, `<NavLink>`, `<Navigate>` and `navigate()` from `useNavigate`. `navigate("..", { relative: "path" })` behaves exactly like the link. `useResolvedPath(to)` and `useHref(to)` expose the result if you need to inspect it. Two further details: - A `to` that has **no pathname**, such as `"?tab=2"` or `"#top"`, resolves against the current URL's pathname, so it keeps the page and only changes the query or hash. - `to="."` points at the current route's own path, which is useful for a "reset" link that clears the query string. ## Absolute paths and basename A `to` beginning with `/` is absolute **within the router**: the router prefixes its `basename`, so an app mounted at `/admin` renders `<Link to="/settings">` as `href="/admin/settings"`. Absolute paths are robust to moving a component, but they hard-code where a feature lives; relative paths let a feature subtree be mounted somewhere else without editing its links. ## Debugging a link that lands in the wrong place When a relative link goes somewhere unexpected, work through it in order: 1. Find the **route that renders the link**, not the route that is currently the leaf. A link in a layout resolves from the layout. 2. List that route's ancestors and drop any that have **no path** (pathless layouts) and any **index** routes. 3. Count the leading `..` segments; each removes **one route**, however many URL segments that route's pattern has. 4. Append the remainder of `to` to the path you reached. 5. Check whether the link passes `relative="path"`, which replaces steps 2 and 3 with plain URL-segment removal. `useResolvedPath(to)` returns the pathname React Router computes, so logging it from the component that renders the link confirms the answer without clicking. ## Summary - Default resolution is by route (`relative="route"`), from the route that renders the link. - `..` removes a whole route's pattern; pathless and index routes do not count. - `relative="path"` gives URL-segment semantics for `..`. - Leading `/` is absolute from the basename; search-only `to` values keep the current path.

  • In React Router v7, EditProfile moves inside a pathless layout route under settings. Does that change where its to=".." goes?
    No. A route without a `path` contributes no URL segment, so route-relative resolution does not count it when climbing. `..` from the `profile/edit` route still lands on `/settings`. Adding or removing pathless layouts to share a shell is therefore safe for existing relative links.
  • In React Router v7, should a reusable feature's links be absolute or relative?
    Relative, where the feature owns its subtree. A feature whose links are relative to its own root route can be mounted under `/settings` or `/admin/settings` without edits. Absolute paths are right for crossing into another feature, where the target does not move with you.

Route-relative ".." is like asking for your manager in an org chart: you reach the next person up, however many job-title words separate you. relative="path" is like walking one door back down the corridor.

saying these in an interview costs you the question

  • ".." in a Link always removes exactly one URL segment, like an href
  • Relative links resolve against the current browser URL, not the route tree
  • Pathless layout routes count as a level when resolving ".."
  • A leading slash ignores the router's basename
  • relative="path" makes a link absolute