all systems operationalv0.17.10
stech/

App shell pattern

Every authenticated page in app/ renders the same chrome: NavBar with brand, ThemeToggle, and UserMenu. When you add a new authed route group, wrap it in <AppShell>.

Why #

Without a shared shell, pages drift — /agents lost its UserMenu and theme toggle for a while, which made navigating between sections feel broken. One component owning the chrome means future widgets (notification bell, command palette, search) get added once.

The component #

Lives at app/components/AppShell.tsx. Client component (UserMenu + ThemeToggle are interactive).

interface AppShellProps {
  user: { email; name; image; platformAdmin };
  organization: { slug; name } | null;
  memberships: Array<{ orgSlug; orgName; role }>;
  sidebar?: React.ReactNode;     // optional left rail
  children: React.ReactNode;
}

It takes session-derived props as inputs. Server components resolve the session via getCurrentSession() and pass the bits AppShell needs.

Pattern #

Each route group has a layout.tsx that:

  1. Resolves the session (getCurrentSession).
  2. Redirects to /sign-in?next=… if missing.
  3. Renders <AppShell> with the session and (optionally) a sidebar.
// app/app/<section>/layout.tsx
import { redirect } from "next/navigation";
import { getCurrentSession } from "@/lib/session";
import { AppShell } from "@/components/AppShell";

export default async function SectionLayout({ children }) {
  const session = await getCurrentSession();
  if (!session?.user || !session.organization) {
    redirect("/sign-in?next=/<section>");
  }
  const { user, organization, memberships } = session;
  return (
    <AppShell user={user} organization={organization} memberships={memberships}>
      {children}
    </AppShell>
  );
}

Sections with a left nav (/settings, /admin) pass it via the sidebar prop:

<AppShell ... sidebar={<SettingsSidebar />}>{children}</AppShell>

The slot forwards to PageShell's sidebar — no need to bring your own layout grid.

Templates #

Existing layouts to copy from:

Public pages #

Auth pages (/sign-in, /sign-up, /invite/[token], /authorize-device, /auth/verify) keep their minimal chrome and don't use AppShell — they have their own (auth) layout. Don't wrap them.