Proxy & Middleware

Jumbo React Next uses a proxy pattern instead of a monolithic middleware.ts file. The proxy is a composable async function in src/proxy.ts that is called from middleware.ts. It handles two concerns in sequence: locale detection/redirect and authentication route guards.

Why a proxy function?

Next.js 16 introduced changes to how middleware interacts with the App Router. The proxy pattern separates each concern into its own handler file (src/proxy/locale.ts, src/proxy/auth.ts), making each independently testable and easier to extend without modifying a single large middleware file.

Request flow

Incoming request
      ↓
middleware.ts
      ↓
proxy(request)  [src/proxy.ts]
      ↓
handleLocale(request)  ← redirect if no locale prefix
      ↓ (if not redirected)
isPublicPath?  ← pass through
      ↓ (if not public)
isAnonymousPath?  ← redirect authenticated users away
      ↓ (if not anonymous)
handleAuth(request)  ← redirect unauthenticated users to login
      ↓
NextResponse.next()  ← proceed to the matched route

proxy.ts

typescript
// src/proxy.ts
import { handleAnonymousRoute, handleAuth } from '@/proxy/auth';
import { handleLocale } from '@/proxy/locale';
import { isAnonymousPath, isPublicPath } from '@/utilities/helpers/path';
import { NextRequest, NextResponse } from 'next/server';
 
const defaultLocale = 'en-US';
 
export async function proxy(request: NextRequest) {
  const { pathname } = request.nextUrl;
 
  // Step 1: redirect if locale prefix is missing
  const localeResponse = await handleLocale(request);
  if (localeResponse) return localeResponse;
 
  // Step 2: public paths — always pass through
  if (isPublicPath(pathname, defaultLocale)) {
    return NextResponse.next();
  }
 
  // Step 3: anonymous paths — redirect authenticated users
  if (isAnonymousPath(pathname, defaultLocale)) {
    const anonymousResponse = await handleAnonymousRoute(request);
    return anonymousResponse ?? NextResponse.next();
  }
 
  // Step 4: protected paths — redirect unauthenticated users
  const authResponse = await handleAuth(request);
  return authResponse ?? NextResponse.next();
}
 
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico|assets/).*)'],
};

Locale handler (src/proxy/locale.ts)

Detects whether the URL has a locale prefix. If not, it redirects to the same path under the default locale (en-US):

typescript
// src/proxy/locale.ts
export async function handleLocale(request: NextRequest) {
  const { pathname } = request.nextUrl;
 
  // Skip API routes and Next.js internals
  if (
    pathname.startsWith('/api/') ||
    pathname.startsWith('/assets/') ||
    pathname.startsWith('/_next/')
  ) return null;
 
  // Already has a locale prefix — no redirect needed
  if (pathnameHasLocale(pathname)) return null;
 
  // Prepend the default locale
  const url = request.nextUrl.clone();
  url.pathname = `/en-US${pathname}`;
  return NextResponse.redirect(url);
}

The pathnameHasLocale helper checks whether the pathname starts with any of the six supported locale codes:

typescript
export function pathnameHasLocale(pathname: string): boolean {
  return locales.some(
    (locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
  );
}

Auth handler (src/proxy/auth.ts)

Reads the Auth.js session and redirects based on authentication state:

typescript
// src/proxy/auth.ts
 
// Called for protected routes — redirect unauthenticated users to login
export async function handleAuth(request: NextRequest) {
  const session = await auth();
 
  if (!session?.user) {
    const url = request.nextUrl.clone();
    url.pathname = '/auth/login-1';
    return NextResponse.redirect(url);
  }
 
  return null;  // authenticated — proceed
}
 
// Called for anonymous routes — redirect authenticated users to the dashboard
export async function handleAnonymousRoute(request: NextRequest) {
  const session = await auth();
 
  if (session?.user) {
    const url = request.nextUrl.clone();
    url.pathname = '/dashboards/crypto';
    return NextResponse.redirect(url);
  }
 
  return null;  // unauthenticated — proceed to the login page
}

Route categories

Routes are classified in src/config/routes/path.ts:

typescript
// src/config/routes/path.ts
export const publicPaths = [
  '/auth/login-1',
  '/auth/forgot-password',
  '/auth/signup-1',
];
 
export const anonymousPaths = [
  '/auth/login-1',
  '/auth/forgot-password',
  '/auth/signup-1',
];

Middleware matcher

The config.matcher in proxy.ts is re-exported and used by middleware.ts. It excludes API routes, _next internals, favicon.ico, and the assets/ folder so the proxy only runs for application routes:

typescript
export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico|assets/).*)'],
};

Customizing route behavior

To make a new route bypass authentication entirely (e.g. a public landing page):

typescript
// src/config/routes/path.ts
export const publicPaths = [
  '/auth/login-1',
  '/auth/forgot-password',
  '/auth/signup-1',
  '/landing',  // ← add the path without locale prefix
];

To skip all auth checks during development:

typescript
// src/config/index.ts
export const CONFIG = {
  // ...
  DISABLE_PROTECTED_ROUTE_CHECK: true,  // bypasses handleAuth in the proxy
};