Middleware (proxy.ts)
apps/web/src/proxy.ts is the Next.js middleware file (renamed from middleware.ts — Next.js 16 uses proxy.ts as the convention). It runs on every matched request before the page is rendered.
What It Does
- Generates a request ID —
x-request-idheader (10-char UUID without dashes) for log correlation across the frontend and API - Detects session presence — reads the session cookie without validating it
- Routes staff subdomain requests — redirects and rewrites for
staff.*subdomains - Routes main domain requests — redirects between portal, login, and staff pages based on session state
Cookie Detection
The middleware detects session presence by checking for the Better Auth session cookie:
// HTTPS (production)
const hasSession = request.cookies.has("__Secure-better-auth.session_token");
// HTTP (local dev)
const hasSession = request.cookies.has("better-auth.session_token");
This is a presence check only — the middleware does not validate or decode the cookie. An expired or tampered cookie would pass this check. The middleware is a routing hint, not a security gate.
Actual session validation happens API-side on every authenticated request via SessionGuard.
Staff Subdomain Routing
Detected by hostname prefix:
const isStaff = hostname.startsWith("staff.") || hostname.startsWith("staff-staging.");
For staff subdomain requests:
/→ rewrite to/staff/portal(not a redirect — URL stays as/)/login,/register,/staff/login,/staff/first-login/*→ allowed through (no auth required)- Any other path without session → redirect to
/staff/login - Any other path with session → allowed through
Main Domain Routing
For requests on the main domain (pcmr.gr, localhost, etc.):
| Condition | Action |
|---|---|
Has session, visiting /login or /register | Redirect to /portal |
Has session, visiting /staff/portal/* | Redirect to /portal (wrong portal) |
No session, visiting /portal/* | Redirect to /login |
No session, visiting /staff/portal/* | Redirect to /staff/login |
| Anything else | Pass through |
Matcher Config
export const config = {
matcher: [
"/portal/:path*",
"/staff/:path*",
"/login",
"/register",
"/((?!_next/static|_next/image|favicon.ico|images|fonts|icons).*)",
],
};
The catch-all excludes static assets (_next/static, _next/image, favicon.ico, images/, fonts/, icons/).
Request ID Propagation
Every request gets an x-request-id header:
const requestId = crypto.randomUUID().replace(/-/g, "").substring(0, 10);
const headers = new Headers(request.headers);
headers.set("x-request-id", requestId);
This ID is:
- Forwarded to NestJS by the proxy route handlers
- Logged by Pino (appears in Loki as
requestId) - Available in browser DevTools for tracing a specific request across frontend + API logs
Why This Is Not a Security Gate
The middleware runs in the Next.js Edge Runtime and cannot:
- Verify JWT signatures
- Query the database
- Call the API with a timeout guarantee
Its role is purely UX — fast redirects to avoid flash of wrong content. Security is enforced at the API layer by SessionGuard.
Do not add security logic (role checks, permission checks) to the middleware. Add it to API endpoints and use getServerSession() in server components.