next.js
next.js copied to clipboard
[Next 13] Server Component + Layout.tsx - Can't access the URL / Pathname
Verify canary release
- [X] 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.19.2 Yarn: 1.22.17 pnpm: N/A Relevant packages: next: 13.0.6 eslint-config-next: 13.0.3 react: 18.2.0 react-dom: 18.2.0
Which area(s) of Next.js are affected? (leave empty if unsure)
App directory (appDir: true)
Link to reproduction - Issues with a link to complete (but minimal) reproduction code will be addressed faster
To Reproduce
Not Applicable
Describe the Bug
In the new /app directory within a layout.tsx or Server (Page) Component is not possible to access essential informations like:
- Current page URL/Pathname: this is especially useful within Layout.tsx if you want for example to be able to highlight the current menu item the user is browsing, or you want to perform some redirect logics based on the page URL
- Current page Params are not accessible from a parent Layout: eg. /app/{param1}/abc/{param2} the layout positioned within /app/layout.tsx can't read {param1} and {param2}
Expected Behavior
Right now without this kind of functionality the layout.tsx purpose is not clear and it looks almost useless in any kind of real world scenario. Something like "useRouter()" within the server components would be awesome..
Which browser are you using? (if relevant)
No response
How are you deploying your application? (if relevant)
No response
Related comment: https://github.com/vercel/next.js/discussions/41745?sort=top#discussioncomment-4077917
Since layouts can wrap multiple routes and don’t rerender on route changes, it cannot take these arguments as props. for the use cases you are looking for, NextJs. Provides some hooks that you can use inside children client components in the layout.
If you want to highlight a selected link, you can use useSelectedLayoutSegment, in the docs : https://beta.nextjs.org/docs/api-reference/use-selected-layout-segment
The useSelectedLayoutSegment hook allows you to read the active route segment one level down from a layout. It is useful for navigation UI such as tabs that change style depending on which segment is active
to get the params, you would have to use it either in the page.tsx server component, or by using usePathname inside a child client component of the Layout.
The question about the utility of a layout, I see this file as for ex a component that wraps your dashboard and first check if you are logged in, (with cookies and all) and can redirect to login if it is the case. As well as provide a shell witch contain a sidebar (for ex), and that sidebar is client component which uses useSelectedLayoutSegment to determine which link should be highlighted.
Thanks a lot for you Answer @Fredkiss3 At the end I resorted to do something like you described but wasn't very sure on the approach. Now at least I'm sure it's the only way to go.
The question about the utility of a layout, I see this file as for ex a component that wraps your dashboard and first check if you are logged in, (with cookies and all) and can redirect to login if it is the case. As well as provide a shell witch contain a sidebar (for ex), and that sidebar is client component which uses useSelectedLayoutSegment to determine which link should be highlighted.
In this specific case I think neither Layouts are useful, for example if you want to redirect the user to the login page and you want to preserve the previously accessed URL, to redirect the user back once logged id, you can't do that, since you can't access the current page URL. At the end I resorted to "middleware.tsx" in order to do that.
At this point I'm thinking layouts just as dummy "html" wrappers easily replaceable by a page.tsx splitted with different components. The benefits of not having to reload/re-render the layout seems trivial against the limitations/boilerplate needed to make them work. But open to change idea on that :)
The thing is that if you use a page.tsx with multiple component (like a Layout component that wraps your entire page), with each page navigation you have (1) a rerender, (2) you loose the benefit of nested layouts since your page will need to have logic for each level of nesting. Instead of that you could delegate the basic layout of your page to a higher layout component and simplify your page logic, which will contains only the logic for the concerned page.
With a page component of a blog with a sidebar with all articles you have :
// app/blog/[slug]/page.tsx
export default async function ArticlePage({params}) {
const sidebarArticles = await getAllArticlesForSidebar();
const currentArticle = await getArticle(params.slug);
return (
<RootLayout>
<BlogLayout sidebarArticles={sidebarArticles}>
<article>
{/* page content ... */}
</article>
</BlogLayout>
</RootLayout>
}
But with layouts, you can put the logic of the sidebar up in the hierarchy :
// app/blog/[slug]/page.tsx
export default async function ArticlePage({params}) {
const currentArticle = await getArticle(params.slug);
return (
<article>
{/* page content ... */}
</article>
}
// app/blog/layout.tsx
export default async function BlogLayout({children}) {
const sidebarArticles = await getAllArticlesForSidebar();
return (
<main>
<SideBar articles={sidebarArticles} />
{/* this will be the content of your ArticlePage component for ex. */}
{children}
</main>
}
// app/layout.tsx
export default async function RootLayout({children}) {
return (
<html>
<head>
<link rel="icon" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
</head>
<body>
{children}
</body>
</main>
}
The Layout will be used by every child of the layout which wraps them and you could nest as many layouts as you need. One other benefit is that, on the server when navigating inside a layout, only the data needed for the children will be refetched (unless forced), but with the first approach, each time you navigate to another route, you have to make multiple requests to fetch your data.
It's extremely counter-intuitive that there's basically no way to get the pathName from a server-side component. usePathname should at the very least be callable from the server-side as well even if it hits a different code path.
I've been exploring Next.js codebase for an article about server context. In order to get access to more request information than headers and cookies, as far as I understand, a small modification would be needed in Next.js code so that the underlying AsyncLocalStorage includes the request URL in addition to cookies and headers. This way you could create a "url()" function. See this file: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/run-with-request-async-storage.ts Edit: up to date file: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/async-storage/request-async-storage-wrapper.ts
Not sure how this would interact with layouts, that's the generic logic that populates the "cookies()" and "headers()" functions basically
I am not sure why this function haven't been implemented yet, maybe there is a good reason for that
We would like to access the current url / path as is in the Layout in order to set specific css variables on the body element
At least for the redirect() e.g. when a user is not logged in, a workaround would be to place it in the layout.js of each "top-level" route segment. It worked nicely for me.
To redirect to login:
// app/some-private-segment/layout.js
import { redirect } from "next/navigation";
import { myFetchWrapper } from "../../utils/myFetchWrapper";
export default async function PrivateLayout(props: any) {
const user = await myFetchWrapper("/auth/current-user");
if (!user?.userId) {
redirect("/user/login");
}
// ...
}
To redirect away from login and signup pages
// app/user/layout.js
import { redirect } from "next/navigation";
import { myFetchWrapper } from "../../utils/myFetchWrapper";
export default async function LoginSignupLayout(props: any) {
const user = await myFetchWrapper("/auth/current-user");
if (user?.userId) {
redirect("/");
}
// ...
}
Back to the original question: the idea of URL and the request object gets a bit confusing due to nested layouts. When the user navigates between pages deeply nested, the higher-level layouts don't necessarily get recomputed (as far as I understand) and all that caching behavior makes this even more difficult to understand what really goes on during a request. But still, maybe some of the request context could be passed as read-only to the server components as props.
We also have the option to use a middleware for this anyway.
I'm also coming here trying to access searchParams from a layout in order to do /login redirection.
Why not Middlewares? I guess often Next.js Middlewares are suggested as a solution for auth redirection, but middlewares don't currently support a Node.js runtime, so they are not useful for cases such as querying a database directly.
Another way of performing a /login redirect using searchParams with lack of props passed to Layout is to:
- Duplicate your logic for detecting a necessary redirect from
login/page.tsxtologin/layout.tsx - If your logic in
login/layout.tsxdetects that a redirect is necessary, only returnprops.childrenin theLayoutand handle the redirect fromlogin/page.tsx
For example:
// login/layout.tsx
import { cookies } from 'next/headers';
export default async function AuthLayout({ children }: { children: React.ReactNode }) {
const sessionToken = cookies().get('sessionToken')?.value;
if (sessionToken) {
// Avoid rendering layout if user already logged in
if (await getUserByToken(sessionToken)) return children;
}
This return children will cause the page to be rendered, which in turn will redirect the user based on the ?returnTo= value in the search parameters:
// login/page.tsx
import { cookies } from 'next/headers';
export default async function LoginPage({
searchParams,
}: {
searchParams: { [key: string]: string | string[] | undefined };
}) {
const sessionToken = cookies().get('sessionToken')?.value;
if (sessionToken) {
// Redirect to `returnTo` search param if user already logged in
if (await getUserByToken(sessionToken)) {
redirect(searchParams.returnTo || '/');
}
// If a user is not found, delete the invalid session token
cookies().delete('sessionToken');
}
@adileo would you be open to editing this issue to add some further details? For example:
- Editing your title + description of this PR to also explicitly mention
searchParams? Thepathnameby itself wouldn't contain that - Adding a StackBlitz demo - @mpereira created a nice demo in #43863 which could possibly be used as a starting point
After these changes I think it's possible to close #43863 as a duplicate...
For anyone looking for a workaround of accessing current url on the serverside layout (appDir: true), here's how we did it using a middleware:
// /middleware.ts
import { NextResponse } from 'next/server';
export function middleware(request: Request) {
// Store current request url in a custom header, which you can read later
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-url', request.url);
return NextResponse.next({
request: {
// Apply new request headers
headers: requestHeaders,
}
});
}
Then use it from inside of a root layout:
// /app/layout.tsx
import { headers } from 'next/headers';
export default function RootLayout() {
const headersList = headers();
// read the custom x-url header
const header_url = headersList.get('x-url') || "";
}
I've been trying @pauliusuza workaround which worked seamlessly BUT have spent a day to discover that it'd disable SSG. if you put headers in your root layout, there's not a single page which will be able to SSG ever ⚠️
Full explanation here:
https://github.com/vercel/next.js/discussions/41745#discussioncomment-4858645
TLDR:
Hi, thanks for opening this issue, we recently upgraded the documentation to explain this behavior a bit better.
Check out the following link (there is also a "Detailed explanation" tab): https://beta.nextjs.org/docs/api-reference/file-conventions/layout#good-to-know
@balazsorban44
Unlike Pages, Layout components do not receive the searchParams prop. This is because a shared layout is not re-rendered during navigation which could lead to stale searchParams between navigations.
Does that mean the template.tsx file will receive the searchParams?
Hi, thanks for opening this issue, we recently upgraded the documentation to explain this behavior a bit better.
Check out the following link (there is also a "Detailed explanation" tab): beta.nextjs.org/docs/api-reference/file-conventions/layout#good-to-know
@balazsorban44 I think these docs only relate to the searchParams prop, right? Eg. not the title of this issue, which is about getting access to the URL and pathname in a Server Component or layout:
[Next 13] Server Component + Layout.tsx - Can't access the URL / Pathname
I am also stuck with this trying to access the current path from a server component and can't seem to find a solution around. I can use it in a client component but it is undefined on some first render
I share the same problem and have a slightly tangential question for devs on this thread:
When working with Next 13 app beta, how do you discover type & prop definitions for Next.js? I'm looking for a technique that is more efficient than browsing every single d.ts file in node_modules/next/...
To my knowledge, Next.js docs does not publish a master list of types.
Until I discovered that this is a bug and/or simply unsupported, I was guessing prop types, hoping that VSCode would autocomplete... ex: LayoutProps LayoutContext PageProps PageContext APIRouteContext
@drewlustro , with next 13, you basically have to type your page yourself. Since the types are rather simple I don't think this is a big issue, but if you want to see which types to use for your pages & layouts you could run next dev and see the generated folder for types at .next/types or else you could run a build and next would error if your types are incorrects. With the plugin you could even have warnings in the editor directly.
Maybe in the future they will publish, or maybe not since with the upcoming advanced routing patterns, it will be difficult to ship a generic type for Layouts and Pages for parallel routes for ex.
@drewlustro But if you want types for layout and other, I inspected myself the generated types by NextJs and created some custom types that I use for my project :
// src/next-types.d.ts
type PageParams = Record<string, string>;
export type PageProps<
TParams extends PageParams = {},
TSearchParams extends any = Record<string, string | undefined>
> = {
params: TParams;
searchParams?: TSearchParams;
};
export type LayoutProps<TParams extends PageParams = {}> = {
children: React.ReactNode;
params: TParams;
};
export type MetadataParams<
TParams extends PageParams = {},
TSearchParams extends any = Record<string, string | undefined>
> = {
params: TParams;
searchParams?: TSearchParams;
};
export type ErrorBoundaryProps = {
error: Error;
reset: () => void;
};
@Fredkiss3 Thanks!
For anyone looking for a workaround of accessing current url on the serverside layout (appDir: true), here's how we did it using a middleware [using a
'x-url'custom header]:
We just implemented something similar to pass the pathname from Middleware to a layout for a restricted area, and then in the layout, redirect to the login with a returnTo query parameter with the location the user tried to access - if the authentication failed eg. if session token is not valid:
Commit: https://github.com/upleveled/next-js-example-winter-2023-vienna-austria/commit/353fa9f5d6a31a5b283a959aec1edad24f4b4717
middleware.ts
import { NextRequest, NextResponse } from 'next/server';
export function middleware(request: NextRequest) {
const requestHeaders = new Headers(request.headers);
// Store current request pathname in a custom header
requestHeaders.set('x-pathname', request.nextUrl.pathname);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
app/restricted/layout.tsx
import { cookies, headers } from 'next/headers';
import { redirect } from 'next/navigation';
import { getUserBySessionToken } from '../../database/users';
export default async function RestrictedLayout(props: { children: React.ReactNode }) {
const headersList = headers();
const cookieStore = cookies();
const sessionToken = cookieStore.get('sessionToken');
const user = !sessionToken?.value ? undefined : await getUserBySessionToken(sessionToken.value);
// Redirect non-authenticated users to custom x-pathname header from Middleware
if (!user) redirect(`/login?returnTo=${headersList.get('x-pathname')}`);
return props.children;
}
Using middleware breaks Client-side Caching of Rendered Server Components.
@balazsorban44
Unlike Pages, Layout components do not receive the searchParams prop. This is because a shared layout is not re-rendered during navigation which could lead to stale searchParams between navigations.
Does that mean the
template.tsxfile will receive the searchParams?
I didn‘t find an open feature request for this, but this seems like it‘d solve a lot of use cases. Are you planning to file one? @y-nk
i asked @leerob a while ago and he replied "we don't know yet, let's see"
So, just to get this clear: If one renders a header with a menu bar in a server component, you are unable to highlight the currently active menu item because there currently is no way of knowing the current url in a server component? For that you HAVE to use a client component?
I guess PHP isn't that bad after all 😉
Need to access pathname in server component and layout. Any solution found please?
@karlhorky N.B. The usage of headers() will opt out of static generation/caching.
This is much needed feature NextJS team. I definitely have a use case for searchParams in a server side layout.
Edit: @karlhorky my use-case is that I need to fetch data from an API using search params to create my query args and then set classes/meta data within the HTML based on the resulting API response that are applicable to all pages within the layout.
@benweiser can you edit your post above to provide a detailed account of that particular use case? This may be taken into consideration when the team considers building this feature.
@benweiser can you edit your post above to provide a detailed account of that particular use case? This may be taken into consideration when the team considers building this feature.
Just to add on this, not supporting access to the full request during dynamic rendering in layouts defeats the purpose of layouts, as it may involve reproducing the same logic to each and every page down the stream. And not allowing it in pages would be even more problematic. Use case can be any situation where you prefer a search parameter to a route parameter, for instance when you have many parameters to take into account. An e-commerce search page is typical of this. Though I understand the issue that during client-navigation, supporting such params is tricky.
More broadly, I tend to advocate for a request-centric approach, even during static rendering, as SSG can be seen as precomputing the result of a bunch of requests, eventhough existing frameworks simplify this to focus only on the URL route parameters. Middleware rewrites thankfully make it possible to go beyond the URL, by injecting relevant parts of the request into a "fake" route param then used for rendering, but this is convoluted.
Client-side, this could translate as checking if the layout need a refresh anytime something that would have altered the initial server render (so, the request) changes. Namely, any url change, so not only route params but also search params, and any cookie change too
To sum it up my opinion: let us access the full request, it's up to us devs to invent the use cases 🚀
( I am conscious it's easier said than done though)