Relative Links in Next.js: A Shortcut for Nested Routes

How to use relative links in Next.js <Link> component, with an example showing simpler navigation.
When working on a Next.js project with deeply nested routes, I wanted a simple way to navigate “up” the hierarchy without composing the entire path every time.
Normally this wouldn't be much of a problem, but in my case, I had dynamic segments in the URL (like /tasks/[id]/details/...
), which meant that I had to
await
theid
param to every page that needed to display links- pass down the
id
to every server component that needed to build links
Also not a huge deal, but it felt unnecessary, so I searched for alternatives.
#Relative Paths with <Link>
At first, I thought this wasn’t possible since the Next.js docs don’t explicitly cover it. But then I tried:
<Link href="..">Back</Link>
And it worked. It navigated me up two levels, but why?
#Standard HTML Behaviour
Then I realized why there was no documentation for this behaviour: It's actually just how anchor tags work in HTML and since the <Link>
is a wrapper around an <a>
tag, the underlying behavior is the same.
#Relative Links in Action
To demonstrate this, imagine you’re on this URL:
https://yourdomain.com/customer/1/details/address
Here’s how different relative links behave:
import Link from "next/link";
export default async function Page() {
return (
<div>
{/* Same folder, sibling site → /customer/1/details/billing */}
<Link href="billing">Billing</Link>
{/* Same folder, parent site → /customer/1/details */}
<Link href=".">Customer Details</Link>
{/* One level up → /customer/1 */}
<Link href="..">Customer</Link>
{/* Two levels up → /customer */}
<Link href="../..">Customer list</Link>
</div>
);
}
#Comparison in practice
Here’s a quick comparison from my project.
Before (absolute paths):
export default async function AddressPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return (
<div>
<h1>Product Address</h1>
{/* ... */}
<Link href={`/customer/${id}/details`}>Back to Details</Link>
<Link href={`/customer/${id}`}>Back to Product</Link>
<Link href="/customer">All customers</Link>
</div>
);
}
After (relative paths):
export default function AddressPage() {
return (
<div>
<h1>Product Address</h1>
{/* ... */}
<Link href=".">Back to Details</Link>
<Link href="..">Back to Product</Link>
<Link href="/customer">All customers</Link>
</div>
);
}
What I like is that the links still work, even when the dynamic segment name changes.