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
// 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):
// 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:
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:
// 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:
// 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',
];The isPublicPath and isAnonymousPath helpers in src/utilities/helpers/path.ts strip the
locale segment from the pathname before comparing against publicPaths and anonymousPaths.
Add paths to those arrays without the locale prefix.
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:
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):
// 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:
// src/config/index.ts
export const CONFIG = {
// ...
DISABLE_PROTECTED_ROUTE_CHECK: true, // bypasses handleAuth in the proxy
};DISABLE_PROTECTED_ROUTE_CHECK: true bypasses all authentication checks. It is only intended
for rapid local development. Always reset this to false before deploying.