Navbar & Footer
Auth-aware Navbar with mobile hamburger, dark mode toggle, dynamic NavLink array, and CTA slot. Server-rendered Footer with dynamic link groups.
Navbar props
| Prop | Type | Default | Description |
|---|---|---|---|
| links | NavLink[] | required | Navigation links - rendered in desktop nav and mobile menu |
| ctaSlot | ReactNode | undefined | Right-side CTA - e.g. 'Get started' button |
| logo | ReactNode | undefined | Brand logo or text - renders left of nav |
| showThemeToggle | boolean | true | Whether to show the dark/light mode toggle |
| showAuth | boolean | true | Whether to show session-aware login/logout |
NavLink config
NavLink type from types/index.ts:
type NavLink = {
label: string
href: string
icon?: string // lucide icon name - optional
external?: boolean // opens in _blank with rel noopener noreferrer
}
const links: NavLink[] = [
{ label: 'Dashboard', href: '/dashboard' },
{ label: 'Settings', href: '/settings' },
{ label: 'GitHub', href: 'https://github.com', external: true },
]Auth awareness
Navbar uses useSession() from next-auth/react:
- status === 'loading': shows skeleton placeholder - no layout shift
- session exists: shows user name/avatar + logout button (calls logoutAction)
- no session: shows Login and Register links
The skeleton placeholder has the same width as the auth buttons to prevent layout shift during session hydration.
Dark mode toggle
ThemeToggle reads from and writes to localStorage under the 'theme' key. It toggles the 'dark' class on document.documentElement.
On first render, uses the mounted pattern to avoid SSR hydration mismatch - renders nothing until mounted, then reads localStorage or system preference.
Footer props
| Prop | Type | Default | Description |
|---|---|---|---|
| links | NavLink[][] | undefined | Array of link groups - each inner array is one column |
| copyrightName | string | undefined | Name used in '© 2025 [name]' |
| logo | ReactNode | undefined | Brand mark in footer |
Usage
app/layout.tsx
import Navbar from '@/components/nav/Navbar'
import Footer from '@/components/footer/Footer'
import Button from '@/components/ui/Button'
const navLinks = [
{ label: 'Dashboard', href: '/dashboard' },
{ label: 'Settings', href: '/settings' },
]
const footerLinks = [
[
{ label: 'Dashboard', href: '/dashboard' },
{ label: 'Settings', href: '/settings' },
],
[
{ label: 'GitHub', href: 'https://github.com', external: true },
{ label: 'Docs', href: '/docs' },
],
]
export default function Layout({ children }) {
return (
<>
<Navbar
links={navLinks}
ctaSlot={<Button size="sm">Upgrade</Button>}
/>
<main>{children}</main>
<Footer links={footerLinks} copyrightName="MyApp" />
</>
)
}