Next.js 13 get current URL path in Server Component

This may be due to the current beta-phase of Next.js 13 app directory, but I am struggling to make a simple nav link with an active state based on the current path name–only with server components. I assumed that there were an API to get the current pathname, just like you can get headers and cookies in Server Components. But alas, I couldn't find any. I know this is possible to do with Client Components. But I wan't to keep the nav and its links server-side rendered for this example. The only alternative I can think of is kind of hacky; create a middleware that sets a header (or cookie) that is then read from that SC. I don't need these routes to be statically rendered anyway. I also tried to make a client-only provider component which would wrap around a nav link (rendered as a SC) and use tailwind's group feature, then use that group to set the nav link style based on the class name or state. This didn't work, however this resulted in the following error:
Error: usePathname only works in Client Components. Add the "use client" directive at the top of the file to use it.
Error: usePathname only works in Client Components. Add the "use client" directive at the top of the file to use it.
It seems that rendering a client component that uses useRouter or usePathname inside a server component isn't allowed (or supported), even if I don't wrap it (???) I am ok if I have to pass a prop from the layout into the main nav. That's not my main concern.
A
Ambushfall361d ago
Hey, I have kind of a solution
'use client';
import Link from "next/link";
import { useSelectedLayoutSegments } from "next/navigation";


const segment = useSelectedLayoutSegments();

const isActive = href === `/${segment}`
console.log(href)
'use client';
import Link from "next/link";
import { useSelectedLayoutSegments } from "next/navigation";


const segment = useSelectedLayoutSegments();

const isActive = href === `/${segment}`
console.log(href)
the full component I use is:
'use client';
import Link from "next/link";
import { useSelectedLayoutSegments } from "next/navigation";
import {toPascalCase} from '../../helpers/toPascalCase'

function classNames(...classes: string[]) {
return classes.filter(Boolean).join(' ')
}


export function NavLinkTailwind({ href, NavItemName }: { href: string; NavItemName: string; }) {
const segment = useSelectedLayoutSegments();

const isActive = href === `/${segment}`
// console.log(href)

return (
<Link href={href} className={classNames(isActive? 'bg-gray-900 text-white dark:bg-gray-400 dark:text-black' : 'dark:text-gray-400 dark:hover:bg-gray-800 text-black hover:bg-gray-700 hover:text-white','px-3 py-2 rounded-md text-2xl font-medium')} aria-current={isActive ? 'page' : undefined}>
{toPascalCase(NavItemName)}
</Link>
);
}
'use client';
import Link from "next/link";
import { useSelectedLayoutSegments } from "next/navigation";
import {toPascalCase} from '../../helpers/toPascalCase'

function classNames(...classes: string[]) {
return classes.filter(Boolean).join(' ')
}


export function NavLinkTailwind({ href, NavItemName }: { href: string; NavItemName: string; }) {
const segment = useSelectedLayoutSegments();

const isActive = href === `/${segment}`
// console.log(href)

return (
<Link href={href} className={classNames(isActive? 'bg-gray-900 text-white dark:bg-gray-400 dark:text-black' : 'dark:text-gray-400 dark:hover:bg-gray-800 text-black hover:bg-gray-700 hover:text-white','px-3 py-2 rounded-md text-2xl font-medium')} aria-current={isActive ? 'page' : undefined}>
{toPascalCase(NavItemName)}
</Link>
);
}
you use this inside of a server component, yes the link itself still renders as a client component you just pass the values from the server component sorry if it's not what you're looking for
H
human361d ago
Yeah, not what I was looking for since it's a client component. Thanks anyway. I did figure out using the provider (I had a usePathname in my nav that is an SC, that's why it failed). But really, I just wonder why we have to go through hoops to get such context.
A
Ambushfall361d ago
Idk, for me personally, it makes all sense to use client components for links, buttons, etc. And to pass data to them from SC Hopefully you find a great workaround!
H
human361d ago
For me it doesn't, because they are non-interactive in terms of JS. They don't enhance the experience as they are just anchors. For buttons, I'd rather use forms to handle data submissions. Makes logic a lot simpler and avoids a ton of footguns that comes with managing state and side effects.
A
alan322d ago
holy crap. I can't believe this is even an issue. I was looking for this and it seems like there's a high demand for knowing the pathname in a server component but vercel dropped the ball on this
A
alan322d ago
GitHub
[Next 13] Server Component + Layout.tsx - Can't access the URL / Pa...
Verify canary release I verified that the issue exists in the latest Next.js canary release Provide environment information Operating System: Platform: linux Arch: x64 Binaries: Node: 19.0.1 npm: 8...
G
Grey322d ago
The soloution I did was just add a x-current-url via my middleware for the headers() to have access to it works as expected
// middleware.ts

import { NextResponse, type NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
// your middleware stuff here
return NextResponse.next({
request: {
headers: new Headers({ "x-url": request.url }),
},
});
}

// page.tsx

import React from "react";
import { headers } from "next/dist/client/components/headers";

export default function Page() {
const _headers = headers();
const currentUrl = _headers.get("x-url");
return (
<div>
<span>Some page at url {currentUrl ?? ""}</span>
</div>
);
}
// middleware.ts

import { NextResponse, type NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
// your middleware stuff here
return NextResponse.next({
request: {
headers: new Headers({ "x-url": request.url }),
},
});
}

// page.tsx

import React from "react";
import { headers } from "next/dist/client/components/headers";

export default function Page() {
const _headers = headers();
const currentUrl = _headers.get("x-url");
return (
<div>
<span>Some page at url {currentUrl ?? ""}</span>
</div>
);
}
@humanest ^
A
alan322d ago
is middleware.ts in /app/middleware.ts?
G
Grey322d ago
yes core root*
A
alan322d ago
I'm seeing null when logging currentUrl
G
Grey322d ago
it should work, I used it the other day and remember it being something like that
A
alan322d ago
hrm.. this is what I have
// layout.tsx
import { headers } from "next/headers";

export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const _headers = headers();
const currentUrl = _headers.get("x-url");
console.log("url>>", currentUrl);
return <div>{children}</div>;
}
// middleware.ts
import { NextResponse, type NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
// your middleware stuff here
console.log("reqqq", request);
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-url", request.url);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
// layout.tsx
import { headers } from "next/headers";

export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const _headers = headers();
const currentUrl = _headers.get("x-url");
console.log("url>>", currentUrl);
return <div>{children}</div>;
}
// middleware.ts
import { NextResponse, type NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
// your middleware stuff here
console.log("reqqq", request);
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-url", request.url);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
and seeing nothing logged for request and null logged for currentUrl 😦 I also tried your code verbatim using "next/dist/client/components/headers";
A
alan322d ago
Stack Overflow
How can I get the url pathname on a server component next js 13
So basically I have a server component in app dir and I want to get the pathname. I tried to do it using window.location but it does not work. Is there any way I can do this?
G
Grey322d ago
the in the world are you passing the request.headers to new Headers() look my at example above clearly 🤨
A
alan322d ago
that's from the linked solution. I tried yours too I'll try yours again and paste it
G
Grey322d ago
well you're clearly doing something wrong, because I just strapped a new next 13 app and it worked
A
alan322d ago
// src/app/middleware.ts
import { NextResponse, type NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
// your middleware stuff here
console.log("reqqq", request);
return NextResponse.next({
request: {
headers: new Headers({ "x-url": request.url }),
},
});
}

// src/app/dashboard/layout.tsx

import { headers } from "next/headers";

export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const _headers = headers();
const currentUrl = _headers.get("x-url");
console.log("url>>", currentUrl);
return <div>{children}</div>;
}
// src/app/middleware.ts
import { NextResponse, type NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
// your middleware stuff here
console.log("reqqq", request);
return NextResponse.next({
request: {
headers: new Headers({ "x-url": request.url }),
},
});
}

// src/app/dashboard/layout.tsx

import { headers } from "next/headers";

export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const _headers = headers();
const currentUrl = _headers.get("x-url");
console.log("url>>", currentUrl);
return <div>{children}</div>;
}
G
Grey322d ago
G
Grey322d ago
I literally copy/pasted your snippet now and it works you're doing something else wrong if it's not working
A
alan322d ago
moving middleware.ts into /src from /src/app fixed it man, I wish the docs were clearer... it says the "root of the project". like does that mean it's colocated with package.json, which would be outside of src? thanks for your help
G
Grey322d ago
root is root of /app or /pages, I should've clarified that subconsciously 🤔
A
alan322d ago
meh. it's on vercel to disambiguate. the root can mean so many things up to the app/pages dir
G
Grey322d ago
well you can always open a PR on their docs now that they're Open-Source 🤔
A
alan322d ago
good point
Want results from more Discord servers?
Add your server
More Posts
argument where of type listingById is missing an argument using prismahere is the code https://github.com/NAVNit125/airbnb-video/blob/main/app/actions/getListingById.tsapp router state managementhow do you use redux / zustand in nextjs app router?need discord bot hosting 24/7 uptime free low specs no card no phoneneed discord bot hosting 24/7 uptime free low specs no card no phoneNeed to disable ESC to close modal on Vue.jsHi, I am having some problems with Vue where I have a modal that's not suposed to close when pressinClerk: Getting the OAuth access and/or refresh token of userThe title should say it all. Does Clerk's user interface even include the two?Type 'string | null' is not assignable to type 'string | undefined'.Argument of type 'Quest' is not assignable to parameter of type '{ id: string; title?: string | undeMatching types to Prisma modelsI have a T3 boilerplate with Prisma and PlanetScale as per Theo's T3 Stack Tutorial. I'm trying to cneed key value pair database like redisneed key value pair datastore like redis which is similar to python dictionary and should be cleint How do I solve this errorI have installed daisyui already. Already tried the suggested fix"Unsafe assignment of `any` valueI am using latest create-t3-app with prisma, tailwind nextAuth and trpcClerk + Next.js app router <SignedOut> children not rendered after <UserButton> sign outHi there! I'm currently building an app with Clerk and I've got signing in working however when I siturbo repo built lint errorI was setting up ct3turbo when I saw the GitHub action workflow is showing errors, how do I fix it?changing col-span with template literalany idea why this isnt working?[Solved] T3 Stack PWA ??Does anybody know how to turn a t3 site into a PWA? I am using next-pwa but I am getting a few errortRPC Error: Invalid hook call.Sorry for the noob question I just cant figure out how to do this I have the following tRPC route: eStack?Hey how would I go about creating a website where you can post minecraft servers and include a imageHow would I order by the role?Hello, I'm trying to set up an API, where I return some users. I've got it working, where it returnsPrisma Create with Connect IssueHey! I wasn't able to find another thread explaining this, but I'd love help with this! I have twoGenerating image background jobI'm making a game where you can create levels in HTML5 Canvas and upload them to the api. I will neeHelp with Vercel deploymentHi folks, I am having issues deploying to vercel. I keep getting this error: