-
Notifications
You must be signed in to change notification settings - Fork 27.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Next 13] Server Component + Layout.tsx - Can't access the URL / Pathname #43704
Comments
Related comment: |
Since layouts can wrap multiple routes and don’t rerender on route changes, it cannot take these arguments as props. If you want to highlight a selected link, you can use
to get the params, you would have to use it either in the 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 |
Thanks a lot for you Answer @Fredkiss3
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 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 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. |
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. 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 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 We also have the option to use a middleware for this anyway. |
I'm also coming here trying to access 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
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 // 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:
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 Full explanation here: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): |
Does that mean the |
@balazsorban44 I think these docs only relate to the
|
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 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: |
@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 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! |
We just implemented something similar to pass the Commit: upleveled/next-js-example-winter-2023-atvie@353fa9f
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,
},
});
}
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. |
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: I guess PHP isn't that bad after all 😉 |
so are they not going to fix this? |
This approach with middleware + call of export const mdFactory: MiddlewareFactory = (next: NextMiddleware) => {
return async (req: NextRequest, event: NextFetchEvent) => {
req = ... // attempt to modify request
...
next(req, event) // this function accepts NextRequest only, does not accept ModifiedRequest
// `req` is a class instance so you can't clone it either
...
} We need a proper solution for the issue.
|
So let me ask this then: If we can't get the URL (at least reliably) to do conditional logic for pages, then how are people getting around this problem? If I want to have a set of "home" pages at Do I just give up and manually add the navbar to each of the Edit: My apologies. I guess I realized I could just do it on the component itself, so I don't have to screw up the entire layout. |
@dark-swordsman For this kind of use case just use route groups: https://nextjs.org/docs/app/building-your-application/routing/route-groups and nested layouts if you want to do it properly. |
Hey everyone, thank you for sharing some of the challenges with accessing We'd like to share some context into our reasoning for not exposing it in Layouts, and discuss how you can implement the use cases you've shared here. Here's a quick overview:
Why doesn't Next.js expose the
|
API | Usage |
---|---|
usePathname | Read the current pathname |
useSearchParams | Read the current query params. |
useParams | Read the dynamic params in the current URL |
useSelectedLayoutSegments | Read the active route segments (children) below the Layout it is called from. |
Examples
- Active Links
Server-side APIs
APIs | Where | Usage |
---|---|---|
params |
page.js component, layout.js component (params above it), generateMetadata |
Statically or dynamically render a page on the server based on dynamic params. |
searchParams |
page.js component, generateMetadata |
Render a page on the server based on the search params of the incoming request. Client navigations that update search params will send a new request to the server and cause a re-render. |
Parallel Routes | - | Render one or more pages within the same layout simultaneously. The terminating segments (page.js ) receive complete params and searchParams and can, therefore, be used for server-side UI that depends on the URL, such as breadcrumbs. |
Examples
- Dynamically setting Metadata
- Use
generateMetadata
and derive fromparams
orsearchParams
(Docs). - Setting locales - by following the recommendations for i18n, you should have access to the locale
params
ingenerateMetadata
(Docs). - Setting canonical URLs - derive from
params
orsearchParams
(e.g. if you need unique URLs for sorting, filtering, pagination) ingenerateMetadata
(Docs).
- Use
- Breadcrumbs
- Handling auth redirects
- We don't recommend doing auth checks in layouts, instead you can do optimistic checks in Middleware and secure checks in a data access layer. (Docs, Demo, Code)
- Sorting, Filtering, and Pagination / Fetching data based on search params
- Rendering layouts for specific pages
- Use Route Groups to group pages that share a layout (Docs).
Since the RSC model is relatively new, it'll take some time for new patterns to emerge. I'll update this comment as docs and examples are added.
If you're trying to implement a pattern not covered above, or are unsure how to use these APIs for your use case, please let us know by opening a discussion. Also, feel free to contribute patterns that have worked for you (🙏).
Hello @delbaoliveira! First of all - thank you for the detailed explanation of the team's position towards this thread. Nevertheless, I still have questions, and the current functionality is still not enough. To avoid writing a long answer here, I described them in a discussion. |
And if someone ever wanted an example of how to over engineer something (complexity over simplicity) this would be one of the best examples I've seen :) |
Let's vote: #65385 |
can we make it clear? tell me my what is the problem of vercel |
Thank you for the detailed response @delbaoliveira! I'm trying to solve the auth-redirect stuff, and going the middleware route seems like it would work. The documentation also seems to have been worked on a lot since last I read, which is very much appreciated. Going the middleware route still seems a little off though. I think the reason I reached for layouts initially is that it integrates with the routing system. Having a To do the same with a middleware, I need to basically re-implement the routing system in a regex, to ensure it runs on the correct paths. I'd also need to remember to update it with new routes, which seems like it could be easy to forget. There's also the limitation that you can only have one middleware. What if I needed to add another middleware for a different set of routes, for another feature/use case? Then I couldn't use the Like last time, the response still doesn't answer why |
Hi, I am facing the exact same issue. I'm using static exports and I just want to wrap all my pages in a HOC. This HOC will add a header and render it differently for each page (highlighting the current page in the nav menu). This 100% works with static exports IF I go to each and every page and do the wrapping myself. This is ridiculous though, this is why we use frameworks. Now, a NextJS template supposedly exists to do exactly that: to wrap every page. Unlike the layout, the template does not persist state; it is different for every page. The template receives the "children" prop. The "children" prop is obtained, by NextJS, based on the current route, ON THE SERVER SIDE. So, there is NO excuse for the template to not receive a "currentRoute" prop together with the "children" prop. I see this issue has been opened for a year, and no real solutions given by the NextJS team. This is crazy. We need a simple way to wrap every page in a route-aware HOC, like you can do in any other frontend or backend framework in existence. Note: This is asking much less than to have access to the "request" or to the "headers". Why can't the template just know what page is being rendered? It DOES know the entire jsx of the page (via the "children" prop) so it is ridiculous that it cannot get the name of the page! The "url" is a client-side concept, the "route" is a server-side concept. The entire purpose of a routing system is to convert a url into a route, or "page". It is absurd that the template cannot know what is the name, path, or whatever, of the page that is currently being rendered inside of it. In other words, what I'm saying is that whatever method in the NextJS codebase that takes the template and wraps the page in it, already knows what the page is, cause it's giving the template it's entire jsx, so it just has to additionally hand over to the template the path of that page (the "route")! |
This, taken from the docs, is a simplified description of what NextJS does behind the scenes: My proposal is: replace
with
so as to allow the developer to access the routeParam in the template. I'm assuming "routeParam" is a string representing the page path in the filesystem. |
I suppose here is not a recommended approach though https://www.propelauth.com/post/getting-url-in-next-server-components |
This is the use-case I'm here for. I need to preserve the originally requested pathname in the redirect that results from an authentication failure (session expired, for example). This is so that the user gets the content they want after they log in. @delbaoliveira, the sample code doesn't address this relatively common scenario. As an aside, it also does the auth check in a layout ( In the context of that demo code, in order to pass the originally requested pathname to |
Okay, my beloved next.js, one more try. Let's do it! createPageContext PR! |
To add to the ridiculousness of the situation, I'm noticing that actual dynamic variables, such as "currentUser", are often used in the layout in working Next.js projects. Why does the Next.js team think that the filesystem path of the current page being rendered under the layout is a "dynamic variable" to begin with? It only updates when there are router changes (which are similar to complete page refresh, or to switching html file in static exports). "children", for example, is a dynamic variable (containing jsx!) that is DEPENDENT on the filesystem path of the current page. After two weeks from my last comment, it stays incomprehensible to me why would they say that layouts cannot access the filesystem path of the current page being rendered under said layout, but even more so for templates, since templates supposedly exist exactly because of layout's limitations. They keep talking about "getPathname", while "pathname", as a concept, does not even make sense for a backend. Backends do not access the "pathname", they access the route. And, emphasis here, they DO access the route. |
@delbaoliveira thanks for this write-up above which explains the current situation in details. However, as the other comments are indicating, this is not totally convincing and not answering the questions raised by the community. I'd like to rephrase some of the questions I have based on these explanations. Sidenote: the topic is sometimes leading to heated discussion, so I'd like to start by stating that I am extremely thankful for the open source work done by Next.js core team and other contributors. We are in the heart of the reactor of what web development may look like in the years to come. This is an exciting thing, not a bad one! So let's imagine we have a
Many of us might prefer losing some performance and have a
I think I don't totally get this sentence. This bothers me because currently we do use the pathname in RSCs by using I've raised the concern that, as to my best knowledge, there is no official guarantee that a page has the same render cycle than the components it contains, similarly to how layouts and pages currently work. This would break this So here are a few questions:
To the community: to be constructive, I think we should work on proposing alternate designs for layouts, PPR and similar features, especially people that have the chance to be knowledgeable both about distributed infrastructure and "backend-for-frontend", if they don't already all work at Vercel :) |
@eric-burtel I don't understand, I feel like the relevant points are being ignored. "the key point here seems to be RSC layouts that do not rerender during navigation. I didn't met a similar approach in the industry so I feel like Next.js is pioneering here. This should be the heart of discussions around access to the request object." If that is the key point, why are we ignoring that templates DO rerender during navigation, as per the docs? I don't understand how this is still unaddressed after 2 weeks of being brought up. Again, it is not about the "request object". It is of course bad for any backend to not be able to access a request object, but it is much worse and more fundamental that the backend cannot access it's own FILEPATH. But let's say for the sake of argument that the issue is merely not being able to access the request object. Why can't the request object be made accessible to the templates? But, again, the real question is: why can't the template know what is the path of the jsx that it is wrapping. |
@crawlchange I think that it's not about the difference between templates and layouts, but about the fact that your component may end up in a layout, even when you don't use one. So the idea of having pure server-side layouts that do not rerender during client navigation imply that you can't have access to the full request within components that may end up in a layout. Regarding request object versus accessing it's "own filepath", any routes knows its path, the problem is that the path may be dynamic and include variables that are only known at request time, the dynamic route parameters. So it's indeed a matter of accessing request information, the URL, which in turns needs some thinking about the impact on the app router and layouts that are shared between multiple dynamic URL. That's why I think that if we want the Some people thinks about switching to Astro because of this but remember that Astro doesn't have a concept of server-rendered layout yet (docs "there is nothing special about a layout component"). Passing params around uses a pattern similar to React |
Hey @eric-burel. When I talk about path, I'm talking about filesystem path. That's the constant confusion that I'm noticing, and that's what I think is severely missing from Next.js' docs. The filesystem path is not dynamic, and it does not include variables that are only known at request time. The filesystem path is where the file is located. This is called a route in backend systems. For example, in Next.js filesystem-based routing, the route of a file that is located in app/users/[id] is /users/[id]. With this I mean the STRING "/users/[id]", NOT the dynamic resolution to "/users/1" or whatever. "/users/[id]" is a single route, not an infinity of possible routes. This single route does correspond to an infinity of possible urls. The urls are dynamic, the ROUTE is NOT. The role of a routing system is to map urls to routes, extract the dynamic parameters and make them available to application code via some prop. The issue with Next.js is that the docs completely ignore that routes exist and that developers might need to access the route. When you search for "how to get the route value in Next.js" you are redirected to getPathname in any search engine. The route is a backend concept, the pathname is a frontend concept, so this conflating of the two is weird. I've seen large threads here where the poor developer just wanted to add a header where he could highlight the current page in the nav. This is not client-side interactivity: the component is only updated on navigation to another page (eg another html file in a static export). Parallel routes and so on is over-engineering this. The templates do re-render between page navigation, and the route is not dynamic, it is one-to-one correspondent to a file in Next.js: it is going to change if and only if a file is renamed or moved somewhere else. With route here I mean the route of the file that is being wrapped by the template, i.e., the route or filesystem path of the page that is currently being rendered. So I don't understand why Next.js completely ignores the concept of routes and doesn't make the routes available to templates, or why this keeps being conflated with client-side concepts such as "pathnames" or "requests". The location of a file in the app folder has nothing to do materially with a client-side request. |
Man, I see so many repositories out there using 'use client' and then stuff like useEffect on the root layout. Next.js allows that, but doesn't allow a, I don't know, "getFilesystemPath" in a mere template 😐 |
This closed issue has been automatically locked because it had no new activity for 2 weeks. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you. |
Edit: Please see the response and solutions here: #43704 (comment)
The text was updated successfully, but these errors were encountered: