> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getsabo.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Routing & Middleware

> Design global route protection, matchers, rewrites/headers, and webhook exclusions with Next.js middleware.

<Info>
  Use middleware to control request flow at the edge: session sync, redirects, headers/rewrites, or selective execution via matchers. This page centralizes patterns used across the app (not just the dashboard).
</Info>

## What middleware does

* **Session sync**: keep Supabase auth cookies in sync for SSR.
* **Redirects**: gate protected pages and bounce signed‑in users away from auth pages.
* **Selective execution**: use matchers to include/exclude paths for performance or correctness.
* **Rewrites/headers**: optionally set security headers or route rewrites (advanced).

## Auth gate pattern (Supabase)

Key logic lives in `sabo/src/lib/supabase/middleware.ts`:

```34:59:sabo/src/lib/supabase/middleware.ts theme={null}
  const {
    data: { user },
  } = await supabase.auth.getUser()

  const protectedRoutes = ['/dashboard']
  const isProtectedRoute = protectedRoutes.some((route) =>
    request.nextUrl.pathname.startsWith(route)
  )

  if (isProtectedRoute && !user) {
    return NextResponse.redirect(new URL('/sign-in', request.url))
  }

  const authRoutes = ['/sign-in', '/sign-up']
  const isAuthRoute = authRoutes.some((route) =>
    request.nextUrl.pathname.startsWith(route)
  )

  if (isAuthRoute && user) {
    return NextResponse.redirect(new URL('/dashboard', request.url))
  }
```

<Tip>
  Keep <code>auth.getUser()</code> directly after the client is created to avoid session flakiness.
</Tip>

## Matcher design (include/exclude strategy)

* Start with the narrowest matcher that achieves your goal (e.g., only `^/dashboard`), then expand if needed.
* Exclude static assets, images, and favicons to reduce overhead.
* Avoid running middleware on webhook endpoints or any route requiring raw request bodies.

Example patterns to consider (adapt to your project root `middleware.ts` when you add one):

```md theme={null}
- Include only dashboard:            ^/dashboard(.*)
- Include all except assets:         /((?!_next/static|_next/image|favicon.ico|.*\.(?:svg|png|jpg|jpeg|gif|webp)$).*)
- Exclude webhooks:                  negative lookahead for ^/api/webhooks/stripe
```

<Warning>
  Overly broad matchers increase latency. Default to including the minimum set of paths; grow carefully.
</Warning>

## Top-level proxy file (used in this repo)

This repository ships with a `src/proxy.ts` that delegates to `updateSession()` and declares a matcher. It is functionally equivalent to a top‑level `middleware.ts`.

```1:19:sabo/src/proxy.ts theme={null}
import { type NextRequest } from 'next/server'
import { updateSession } from '@/lib/supabase/middleware'

export async function proxy(request: NextRequest) {
  return await updateSession(request)
}

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     * Feel free to modify this pattern to include more paths.
     */
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
}
```

## Webhooks (Stripe or Polar)

Webhook handlers (e.g., <code>/api/webhooks/stripe</code> or <code>/api/webhooks/polar</code>) verify signatures using the raw request body. Our default matcher in `src/proxy.ts` currently runs on all paths except static assets, so it **will** execute before these webhook routes. Because `updateSession()` never touches the request body, this works out of the box, but you can exclude the endpoints for extra safety:

```ts src/proxy.ts theme={null}
export const config = {
  matcher: [
    "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$|api/webhooks/stripe|api/webhooks/polar).*)",
  ],
};
```

<Tip>
  If you keep the default matcher, make sure the webhook handlers themselves perform signature verification using the raw `request` (see `/api/webhooks/stripe/route.ts` or `/api/webhooks/polar/route.ts`). Only add the exclusion once you’re ready so you don’t unintentionally bypass auth on other API routes.
</Tip>

## Roles / Tenant guards (skeleton)

You can extend the auth gate with role or organization checks:

```ts theme={null}
// Pseudo-code inside middleware.ts after user fetch
// const { role, orgId } = await fetchUserClaims(user.id) // via server fetch or cached store
// if (request.nextUrl.pathname.startsWith("/admin") && role !== "admin") {
//   return NextResponse.redirect(new URL("/dashboard", request.url))
// }
// if (request.nextUrl.pathname.startsWith(`/org/${orgId}/`) === false) {
//   return NextResponse.redirect(new URL(`/org/${orgId}/dashboard`, request.url))
// }
```

<Info>
  Prefer performing heavy data lookups in route handlers or layouts; keep middleware light. Cache claims if possible.
</Info>

## Security & performance checklist

* **Correct scope**: Match only the routes you need; exclude static and webhook paths.
* **Early auth fetch**: Call <code>auth.getUser()</code> immediately after creating the client.
* **No heavy work**: Avoid DB lookups or large network calls in middleware.
* **Predictable redirects**: Keep redirect targets simple and relative.
* **Webhook integrity**: Do not wrap webhook paths with auth middleware; perform signature verification inside the route.

## See also

* <a href="/core-features/auth-with-supabase">Auth with Supabase</a> (flows, UI, server actions) — route protection is summarized there and links back here for details.
