Header and Navigation
Navigation tells users where they are and where they can go. An auth-aware header shows different options based on login stateβsign in buttons for guests, account links for users. A sidebar in the protected area helps users navigate between account, billing, and premium features.
Outcome
Understand how the auth-aware header and protected area sidebar work in the starter.
Fast Track
- Review
components/header.tsx- already in the starter with auth state check - Review
components/protected-sidebar.tsx- already in the starter with navigation links - Understand how auth-aware navigation works
Hands-on Exercise 4.2
Review and understand the navigation system (pre-built in starter):
What's already implemented:
- Header shows different content based on auth state
- User email displays when signed in
- Sidebar in protected area links to Account, Membership, Subscription, Field Guide
- Active state indicated on current page link
- Field Guide link disabled until user has active subscription
Review hints:
- Header is a Server Component that checks auth with
createSupabaseClient() - Sidebar uses an
InPageSidebarcomponent that handles active states - The
AuthPageSignOutButtoncomponent is used throughout for sign-out
Try It
-
Test header (signed out):
- Sign out of the app
- Visit http://localhost:3000
- Header should show "Sign In" button
-
Test header (signed in):
- Sign in to the app
- Header should show your email and account link
-
Test sidebar navigation:
- Visit http://localhost:3000/protected
- Sidebar should show all navigation links
- Current page link should be highlighted
-
Test active states:
- Click each sidebar link
- Active indicator should update to current page
Done-When
- Understand how the Header checks auth state server-side
- Understand how the sidebar gates the Field Guide link
- Header shows different content when signed in vs signed out
- Sidebar navigation links work correctly
- Active page is visually indicated in sidebar
Solution
The header and sidebar are pre-built in the starter. Here's how they work:
Header Component
The header in components/header.tsx checks auth state server-side:
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { createSupabaseClient } from "@/utils/supabase/server";
export default async function Header() {
// Try to get the user, gracefully handle if Supabase isn't implemented
let user = null;
try {
const supabase = await createSupabaseClient();
const { data } = await supabase.auth.getUser();
user = data.user;
} catch {
// Supabase client not implemented yet - show logged out state
}
return (
<nav className="border-b w-full h-16 shrink-0 flex items-center">
<div className="px-6 w-full flex items-center justify-between mx-auto">
<Link href="/" className="text-sm font-medium flex items-center gap-2">
<span>πΏ</span>
Forager's Guild
</Link>
<div className="flex items-center gap-2">
{user ? (
<>
<span className="text-sm text-muted-foreground hidden sm:inline">
{user.email}
</span>
<Button variant="outline" size="sm" asChild>
<Link href="/protected">My Guild</Link>
</Button>
</>
) : (
<>
<Button variant="outline" asChild>
<Link href="/sign-in">Sign In</Link>
</Button>
<Button asChild>
<Link href="/sign-up">Join Guild</Link>
</Button>
</>
)}
</div>
</div>
</nav>
);
}Key patterns:
- Server Component uses
createSupabaseClient()for auth check - Try/catch wrapper allows the header to work before Supabase is implemented
- Conditional rendering based on
userstate
Protected Sidebar Component
The sidebar in components/protected-sidebar.tsx uses a reusable InPageSidebar component:
import InPageSidebar from "@/components/in-page-sidebar";
export default async function ProtectedSidebar() {
// TODO: Uncomment when hasActiveSubscription is implemented in Section 2:
// const supabase = await createSupabaseClient();
// const hasAccess = await hasActiveSubscription(supabase);
const hasAccess = false; // Hardcoded until Section 2
return (
<InPageSidebar
basePath="/protected"
items={[
{ label: "My Account", href: "/" },
{ label: "Membership", href: "/pricing" },
{ label: "Subscription", href: "/subscription" },
{ label: "Field Guide", href: "/paid-content", disabled: !hasAccess },
]}
/>
);
}Key patterns:
InPageSidebarhandles active state detection automaticallydisabledprop gates the Field Guide until subscription is active- Will integrate with
hasActiveSubscription()after Section 2
Layout Structure
The layouts are already configured in the starter:
Root Layout (app/layout.tsx) includes the Header in the component tree.
Protected Layout (app/protected/layout.tsx) includes the sidebar:
import Content from "@/components/content";
import ProtectedSidebar from "@/components/protected-sidebar";
export default function ProtectedLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<Content>
<div className="flex w-full h-full">
<ProtectedSidebar />
<div className="flex-1">{children}</div>
</div>
</Content>
);
}Navigation Architecture
app/layout.tsx
β
<Header /> (Server Component)
β
Check auth with createSupabaseClient()
β
Render: πΏ Forager's Guild | [Sign In] [Join Guild] (guest)
πΏ Forager's Guild | email | [My Guild] (signed in)
app/protected/layout.tsx
β
<ProtectedSidebar /> (Server Component)
β
<InPageSidebar /> handles active state
β
Render navigation links with active indicators
File Structure
These components are pre-built in the starter:
components/
βββ header.tsx β Auth-aware header
βββ protected-sidebar.tsx β Protected area sidebar
βββ in-page-sidebar.tsx β Reusable sidebar component
βββ auth-sign-out-button.tsx
βββ field-guide-card.tsx
βββ pricing-card.tsx
βββ ui/
βββ button.tsx
βββ card.tsx
app/
βββ layout.tsx β Includes Header
βββ protected/
βββ layout.tsx β Includes ProtectedSidebar
Troubleshooting
Header shows wrong auth state:
- Server components cache aggressively in dev
- Try hard refresh (Cmd+Shift+R / Ctrl+Shift+R)
- Clear cookies and sign in again
- Make sure you've implemented
createSupabaseClientin Section 1
Header shows signed-out state even when signed in:
- Verify
createSupabaseClientis implemented (not throwing an error) - The header has a try/catch that falls back to logged-out state on error
Sidebar active state not updating:
- The
InPageSidebarcomponent handles this automatically - Try navigating via links rather than direct URL entry
- Hard refresh if state seems stuck
Field Guide link always disabled:
- This is expected until you implement
hasActiveSubscription()in Section 3 - Once implemented, uncomment the subscription check in
protected-sidebar.tsx
Was this helpful?