05 / 10

How do you implement role-based route protection — what's the difference between doing it in middleware vs layout vs page?

Role-based route protection in Next.js can be implemented at three levels — Middleware, Layout, and Page. Each has different capabilities, trade-offs, and ideal use cases. Middleware runs earliest at the Edge before any rendering, Layout protects entire route segments with server-side data access, and Page gives the most granular per-page control. The best production apps combine all three layers as a defense-in-depth strategy.

Think of role-based protection as layers of security. Middleware is the bouncer at the front door — fast, early, but limited in what it can verify. Layout is the security desk inside — has access to your full database and session. Page is the room itself — most granular, knows exactly what content to show or hide per role. Using only one layer is fragile. Using all three together creates a robust, secure, and performant protection system.

The Three Layers of Protection
  1. 1

    Middleware — runs at Edge before rendering, best for fast redirects based on token/cookie presence

  2. 2

    Layout — runs on server with full DB access, best for role verification across route segments

  3. 3

    Page — runs on server or client, best for granular per-page role checks and UI-level protection

Layer 1 — Middleware Protection (Edge Level)
Layer 2 — Layout Protection (Server Component Level)
Layer 3 — Page Level Protection (Most Granular)
Reusable Auth Helper — Used Across Layouts and Pages
Client Component Role Gating — UI Level
Complete Defense-in-Depth Example
Middleware vs Layout vs Page — Side by Side
  1. 1

    Middleware — runs at Edge before rendering, fastest, no DB access, best for token/cookie presence checks

  2. 2

    Middleware — can redirect before any HTML is generated, lowest latency protection

  3. 3

    Middleware — cannot verify role from DB, only from JWT claims (which can be stale)

  4. 4

    Layout — runs on server with full DB and session access, protects entire route segments

  5. 5

    Layout — re-runs only when segment mounts, not on every child navigation

  6. 6

    Layout — best for segment-level protection where all child routes need same role

  7. 7

    Page — most granular, per-page permission checks with specific data scoping

  8. 8

    Page — can use notFound() to hide existence of a page entirely from unauthorized users

  9. 9

    Page — UI-level gating to show/hide buttons and sections based on permissions

Critical Security Rules
  1. 1

    Never rely on client-side role checks alone — always back up with server-side verification

  2. 2

    Never trust JWT role claims without DB verification for sensitive operations — roles can change

  3. 3

    Use notFound() instead of redirect('/forbidden') to hide existence of sensitive pages

  4. 4

    Always scope data queries to the authenticated user — never return data outside their org/tenant

  5. 5

    Middleware alone is not enough — JWT can be stale if role changed after token was issued

  6. 6

    Defense in depth — always use at least two layers (middleware + layout or middleware + page)

  7. 7

    Log all unauthorized access attempts for security auditing