How to Implement Protected Routes with Middleware in Next.js on Cloudflare
You can implement protected routes in Next.js on Cloudflare by creating a middleware.ts file that validates Better-Auth sessions at the edge and redirects unauthenticated users before they reach protected pages.
The ifindev/fullstack-next-cloudflare repository demonstrates a production-ready pattern for implementing protected routes with middleware in Next.js on Cloudflare. By combining Next.js Middleware with Better-Auth running on Cloudflare Workers, authentication checks execute at the edge—ensuring low latency and preventing protected resources from ever reaching unauthenticated clients.
Setting Up the Middleware Entry Point
Every protected route implementation starts with the middleware entry point. The middleware.ts file at the project root intercepts incoming requests before they reach your page components.
The middleware initializes by importing NextRequest and NextResponse from next/server, along with the getAuthInstance utility from @/modules/auth/utils/auth-utils:
import { type NextRequest, NextResponse } from "next/server";
import { getAuthInstance as getAuth } from "@/modules/auth/utils/auth-utils";
This file serves as the gatekeeper for your application, running on Cloudflare's edge runtime to evaluate authentication status before any rendering occurs.
Configuring the Better-Auth Singleton
The authentication logic relies on a singleton pattern to maintain a single Better-Auth instance across requests. In src/modules/auth/utils/auth-utils.ts, the getAuthInstance function creates or reuses a cached Better-Auth configuration that knows how to read session cookies stored by the client.
This singleton approach prevents unnecessary re-initialization of the auth provider on every request, which is critical for performance in a serverless edge environment like Cloudflare Workers. The utility exports several helper functions including getCurrentUser, requireAuth, and isAuthenticated for use across server actions and API routes.
Implementing Session Validation Logic
Inside middleware.ts, the core protection logic validates sessions by calling auth.api.getSession() with the incoming request headers. If the session is valid, the request proceeds; if not, the user redirects to the login page.
The config.matcher array restricts this protection to specific routes, such as the dashboard area:
export async function middleware(request: NextRequest) {
try {
const auth = await getAuth();
const session = await auth.api.getSession({ headers: request.headers });
if (!session) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
} catch {
return NextResponse.redirect(new URL("/login", request.url));
}
}
export const config = {
matcher: ["/dashboard/:path*"],
};
Because this executes on Cloudflare's edge network, the redirect happens before any page JavaScript downloads or executes, providing immediate protection for sensitive routes.
Reusing Authentication Logic in Server Actions
Beyond middleware, the same authentication utilities protect server actions and API routes. The src/modules/auth/utils/auth-utils.ts file exports getCurrentUser for optional session checks and requireAuth for mandatory authentication.
To conditionally access user data in a server action:
import { getCurrentUser } from "@/modules/auth/utils/auth-utils";
export async function someServerAction() {
const user = await getCurrentUser();
if (!user) {
throw new Error("User not authenticated");
}
// Access user.id, user.email, etc.
}
For actions that always require authentication, use requireAuth to enforce login:
import { requireAuth } from "@/modules/auth/utils/auth-utils";
export async function createTodoAction(data) {
const user = await requireAuth(); // Throws if not logged in
// Create todo belonging to user.id
}
This pattern ensures consistent authentication logic across your entire Next.js application, whether running at the edge in middleware or in serverless functions.
Summary
- Create
middleware.tsat your project root to intercept requests before they reach protected pages. - Use a singleton pattern via
getAuthInstanceinsrc/modules/auth/utils/auth-utils.tsto maintain a cached Better-Auth configuration. - Validate sessions by calling
auth.api.getSession({ headers: request.headers })and redirect to/loginwhen validation fails. - Configure
config.matcherto specify which routes require protection, such as/dashboard/:path*. - Reuse auth utilities (
getCurrentUser,requireAuth) in server actions and API routes for consistent security across your Cloudflare-hosted Next.js application.
Frequently Asked Questions
How does the middleware know which routes to protect?
The config.matcher export in middleware.ts defines URL patterns that trigger the authentication check. In the ifindev/fullstack-next-cloudflare repository, the matcher array ["/dashboard/:path*"] ensures only dashboard routes and their subpaths undergo session validation, while public pages like /login remain accessible without authentication.
What happens if the Better-Auth instance fails to initialize in the middleware?
The middleware wraps authentication logic in a try-catch block. If getAuth() throws an error due to initialization failure, the catch block immediately redirects the user to /login. This fail-safe prevents unauthorized access when the auth system is unavailable or misconfigured.
Can I use the same authentication check in API routes and server actions?
Yes. The src/modules/auth/utils/auth-utils.ts file exports getCurrentUser and requireAuth functions that reuse the same singleton Better-Auth instance used by the middleware. This ensures consistent session validation whether you're protecting edge middleware, server actions, or API routes running on Cloudflare Workers.
Why run authentication checks at the edge with Cloudflare Workers?
Running checks at the edge via Cloudflare Workers eliminates latency by validating sessions geographically close to the user before any page rendering occurs. This approach prevents protected HTML, JavaScript, or data from ever reaching an unauthenticated client, offering superior security and performance compared to client-side authentication checks.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →