Pathname from layout.ts at new app structure (nextjs 13) #43657
-
Hi! I'm trying to write a simple "middleware" to protect some routes in a demo app using nextjs 13. The way I'm going about this is trying to get the current path I'm loading into via serverside in a layout.ts at "/" level. Since everything that happens inside this layout.ts happens in all pages, is server side and I'm already getting the user info, so I know if he is logged in or not, it seemed like a good idea. However I could not find a way to get the current path to check if I'm not already at a login page or not. The ideia is simple:
Is there a way to get the current path server side in a layout? |
Beta Was this translation helpful? Give feedback.
Replies: 16 comments 43 replies
-
Looking for it too 😢 |
Beta Was this translation helpful? Give feedback.
-
I had a similar problem. I was thinking that it would be similar to access to the request headers (but the requestStore does not contain that information) So probably we need a new server component function :( |
Beta Was this translation helpful? Give feedback.
-
I need the pathname for highlighting current page in header navigation. |
Beta Was this translation helpful? Give feedback.
-
Took me a while to find: |
Beta Was this translation helpful? Give feedback.
-
Need the same to get the current page url for SEO purpose. |
Beta Was this translation helpful? Give feedback.
-
Hi! Couple days ago I faced the same issue and didn't find anything about that. So here is my solution: In the // middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export default function middleware(request: NextRequest) {
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-next-pathname', request.nextUrl.pathname);
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
}
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}; // layout.tsx
import { PropsWithChildren } from 'react';
import getUser from '@/lib/get-user';
import { headers } from 'next/headers';
import { redirect } from 'next/navigation';
export default async function Layout({ children }: PropsWithChildren) {
const user = await getUser();
const pathname = headers().get('x-next-pathname') as string;
if (!user && pathname !== '/login') {
redirect('/login');
}
if (user && pathname === '/login') {
redirect('/');
}
return (
// ...
)
} |
Beta Was this translation helpful? Give feedback.
-
This is what has worked for me:
|
Beta Was this translation helpful? Give feedback.
-
If this helps anyone in the future. My stack is:
// /app/layout.tsx
import './globals.css'
import React from 'react'
import { Inter } from 'next/font/google'
import {Providers} from './providers'
import {cookies} from 'next/headers'
import MainNav from "@/components/main-nav";
import UserNav from "@/components/user-nav";
import { headers } from "next/headers";
import AuthActionButton from "@/components/AuthActionButton";
const inter = Inter({ subsets: ['latin'] })
export default async function RootLayout({
children,
}: {
children: React.ReactNode
}): Promise<Element> {
const isLoggedIn: boolean = cookies().has('pb_auth')
return (
<html lang="en" className="h-full" suppressHydrationWarning>
<body className="h-full">
<Providers>
<div className="border-b">
<div className="flex h-16 items-center px-4">
<MainNav className="mx-6"/>
<div className="ml-auto flex items-center space-x-4">
{!isLoggedIn && <AuthActionButton />}
{isLoggedIn && <UserNav />}
</div>
</div>
</div>
<main className="mx-auto">{children}</main>
</Providers>
</body>
</html>
)
} // /components/AuthActionButton.tsx
'use client'
import {Link} from "@nextui-org/react";
import {usePathname} from "next/navigation";
import {JSX} from "react";
import {Button} from "@/components/ui/button";
export default function AuthActionButton(): JSX.Element {
const pathname: string = usePathname()
return (
<Link href={pathname === '/auth/signin' ? '/auth/signup' : '/api/auth/signin'}>
<Button>{pathname === '/auth/signin' ? 'Sign up' : 'Sign in'}</Button>
</Link>
)
} |
Beta Was this translation helpful? Give feedback.
-
I cant believe this is not a thing and that there is no feedback from nextjs. Surely the server should know what route is being hit otherwise how does it know which pages to render? Doesnt make sense that you cannot get the url or pathname of a route in a server component. How do you use this thing: |
Beta Was this translation helpful? Give feedback.
-
In the same vein as path names not being available in layouts... query params too:
To me, this makes nested layouts that do anything beyond rendering HTML i.e. data fetching completely pointless because you can't use the url to drive the state of your application for your data fetching.. like every good web app should do. Back to client side data fetching to avoid the chaos |
Beta Was this translation helpful? Give feedback.
-
Edit: Also see #43704 (comment)If you need to access to the pathname outside the 'use client'
import { usePathname } from 'next/navigation'
// This a client component, still prerendered
export function Pathname({ children }) {
const pathname = usePathname()
return (
<div>
<p>Path: {pathname}</p>
{children}
</div>
)
}
---
// Some nested layout where you don't have `params` and `searchParams` as props
// The child page can be a server component!
export default function DashboardLayout({ children }) {
return (
<div>
<Pathname>
{children}
</Pathname>
</div>
)
}
|
Beta Was this translation helpful? Give feedback.
-
I don't understand why there can't be a function from export default async function Layout({children}) {
const path = pathname();
const session = await getSession();
if (!session) {
redirect(`/login?redirect=${path}`);
}
return <div>{children}</div>
} I don't want to use middleware because I don't want it to be global for all pages and have to select which routes it runs for, that's a nighmare to maintain. I also don't really trust the middleware runtime at this point. Having this in RSC would be so simple IMO. |
Beta Was this translation helpful? Give feedback.
-
That's too strange to close this issue w/o answering to the real question. Let's vote to make it open again |
Beta Was this translation helpful? Give feedback.
-
Ideally, any server component should have access to the parameters, even the RootLayout |
Beta Was this translation helpful? Give feedback.
-
I encountered the same problem during development and think I have found a solution. My use-case: I'm developing a website where the client's request was to dynamically display their logo in the header section of the page, depending on the URL slug. Putting the header component in the RootLayout seemed obvious since it always belongs there. Then, I ran into the problem of not being able to access the URL of the displayed page in ServerComponent. I tried a lot of things, but finally, I realized that I made a theoretical error. The solution, in my case, was to take the type PageParams = {
slug: string
}
export default async function Layout({
children,
params,
}: {
children: React.ReactNode
params: Promise<PageParams>
}) {
console.log('params', await params)
return <div>{children}</div>
} The disadvantage of this solution is that you have to define a separate header in the layout for each subpage. Hope this helps someone. |
Beta Was this translation helpful? Give feedback.
-
I had a similar situation where I needed to render a different header and footer for specific routes. In my case, I couldn’t use ![]() |
Beta Was this translation helpful? Give feedback.
Edit: Also see #43704 (comment)
If you need to access to the pathname outside the
page
, the solution is tousePathname()
.