Search code examples
javascriptreactjstypescriptnext.jssupabase

Conditionally Render Navigation Links Based on User Authentication in Next.js


I'm working on a Next.js project where I want to conditionally render navigation links in the header based on user authentication. I have a NavBar that handles the rendering of links.

For authenticated users, I want to display the links Home,Profile, and Sign out. For non-authenticated users, the links should be Login and View All Stores.

Currently, I'm using the session to determine whether a user is authenticated or not. However, when the user is not authenticated, the entire navbar is hidden. I want to modify this behavior so that the navbar is always visible, but only the links are conditionally displayed based on authentication status.

Layout.tsx

export const inter = Inter({ subsets: ['latin'] });

//tried adding this
export const revalidate = 0;
export const dynamic = "force-dynamic";

export const metadata = {
  title: 'Water Refilling Station',
  description: 'Generated by Next.js',
}

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {

  const supabase = createServerComponentClient({cookies});

  const {
    data: { session },
  } = await supabase.auth.getSession();

  const accessToken = session?.access_token || null;
  return (
    <html lang="en">
      <body className={`${inter.className} antialiased`}>
      <NavBar session={session}/>
      <AuthProvider accessToken={accessToken}>{children}</AuthProvider>
      </body>
    </html>
  )
}

NavBarComponent 'use client'

const navigation = [
  { name: 'Home', href: '/', },
  { name: 'Profile', href: '/Profile'},
]

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

export default function NavbarComponent() {
    const pathname = usePathname()
  return (
    <Disclosure as="nav" className="bg-gray-800">
      {({ open }) => (
        <>
          <div className="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
            <div className="relative flex h-16 items-center justify-between">
              <div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
                {/* Mobile menu button*/}
                <Disclosure.Button className="relative inline-flex items-center justify-center rounded-md p-2 text-gray-400 hover:bg-gray-700 hover:text-white focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white">
                  <span className="absolute -inset-0.5" />
                  <span className="sr-only">Open main menu</span>
                  {open ? (
                    <XMarkIcon className="block h-6 w-6" aria-hidden="true" />
                  ) : (
                    <Bars3Icon className="block h-6 w-6" aria-hidden="true" />
                  )}
                </Disclosure.Button>
              </div>
              <div className="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
                <div className="flex flex-shrink-0 items-center">
                  <img
                    className="h-8 w-auto"
                    src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=500"
                    alt="Your Company"
                  />
                </div>
                   <div className="hidden sm:ml-6 sm:block">
                   <div className="flex space-x-4">
                     {navigation.map((item) => (
                       <Link
                         key={item.name}
                         href={item.href}
                         className={
                            item.href === pathname
                              ? 'bg-gray-900 text-white rounded-md px-3 py-2 text-sm font-medium'
                              : 'text-gray-300 hover:bg-gray-700 hover:text-white rounded-md px-3 py-2 text-sm font-medium'
                          }
                          aria-current={item.href === pathname ? 'page' : undefined}
                       >
                         {item.name}
                       </Link>
                     ))}
                   </div>
                 </div>
              </div>
              <div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
              <form action="/auth/signout" method="post">
                  <button type="submit"
                  className="relative rounded-full bg-gray-800 p-1 text-gray-400 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-gray-800"
                  >
                    Sign out
                  </button>
                </form>
                
               </div>
            </div>
          </div>
          

          <Disclosure.Panel className="sm:hidden">
            <div className="space-y-1 px-2 pb-3 pt-2">
              {navigation.map((item) => (
                <Disclosure.Button
                  key={item.name}
                  as="a"
                  href={item.href}
                    className={classNames(
                    item.href === pathname ? 'bg-gray-900 text-white' : 'text-gray-300 hover:bg-gray-700 hover:text-white',
                    'block rounded-md px-3 py-2 text-base font-medium'
                  )}
                  aria-current={item.href === pathname ? 'page' : undefined}
                >
                  {item.name}
                </Disclosure.Button>
              ))}
            </div>
          </Disclosure.Panel>
        </>
      )}
    </Disclosure>
  )
}

To be able to use the session, I added this component and this is the one that is being imported to the Layout.tsx. I am using this component because I cannot directly use the session on the NavBarComponent because session is from a server. Hence, it will cause an error if I try to use the session on the NavBarComponent

import NavbarComponent from "./NavBarComponent";

const NavBar = async ({ session} : {session: any}) => {  
    return ( 
        <div>
            {/* Links here */}
            
            {session &&  <NavbarComponent/>}
            {/* Button for Login and Logout */}
        </div>
     );
}
 
export default NavBar;

I want to ensure that the navbar is always visible and only the links are conditionally displayed based on the user's authentication status. What modifications should I make to achieve this behavior?

Any help or suggestions would be greatly appreciated. Thank you!


Solution

  • You can pass your session to your NavbarComponent as well and inside make the links change, something like this

    export default function NavbarComponent(props: { session: any }) {
      const authenticatedNavigationItems = [
        { name: 'Home', href: '/', },
        { name: 'Profile', href: '/Profile'},
      ];
      const notAuthenticatedNavigationItems = [
        { name: 'View all stores', href: '/stores', } // I'm just making this out as example
      ];
      const navigationItems = session ? authenticatedNavigationItems : notAuthenticatedNavigationItems
     ...
    }
    

    Then in your NavBar change it like this

    import NavbarComponent from "./NavBarComponent";
    
    const NavBar = async ({ session} : {session: any}) => {  
        return ( 
            <div>
                {/* Links here */}
                
                <NavbarComponent session={session} />
                {/* Button for Login and Logout */}
            </div>
         );
    }
     
    export default NavBar;
    

    This way only the items will change but the navbar will always be visible