No history yet

Configuring Clerk Middleware

Configuring Your Middleware

In a Next.js application using Clerk, middleware is your first line of defense. It intercepts every incoming request before it reaches a page or API route, allowing you to check the user's authentication status. The primary tool for this is the clerkMiddleware() function, which you'll place in a middleware.ts file at the root of your project (or in the src directory).

import { clerkMiddleware } from "@clerk/nextjs/server";

export default clerkMiddleware();

export const config = {
  // The following matcher runs middleware on all routes
  // except static assets.
  matcher: [ '/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};

This basic setup makes your entire application private by default. Any unauthenticated user trying to access any page will be automatically redirected to your sign-in page. This is a secure and common approach, but often you need to specify certain routes that should be accessible to everyone.

To make specific routes public, you pass an object with a publicRoutes array to clerkMiddleware().

import { clerkMiddleware } from "@clerk/nextjs/server";

export default clerkMiddleware({
  publicRoutes: ["/", "/sign-in", "/sign-up"]
});

export const config = {
  matcher: [ '/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};

In this example, the homepage, sign-in, and sign-up pages are now accessible to unauthenticated users, while all other routes remain protected.

Grouping Routes with a Matcher

As your application grows, managing a long array of public routes can become cumbersome. You might have groups of routes, like all your API endpoints, that share the same protection rules. Clerk provides a createRouteMatcher utility to simplify this.

import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isProtectedRoute = createRouteMatcher([
  '/dashboard(.*)',
  '/forum(.*)',
]);

export default clerkMiddleware((auth, req) => {
  if (isProtectedRoute(req)) auth().protect();
});

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};

Here, instead of listing every possible dashboard or forum URL, we use a pattern. createRouteMatcher takes an array of route patterns and returns a function. This function then checks if the current request URL matches any of the patterns. This is much cleaner and more scalable.

Inverting the Logic

Sometimes, a "private-by-default" approach isn't what you need. For applications like a blog or a public-facing marketing site, you might want it to be "public-by-default," only protecting a few specific routes like /admin or /settings.

To achieve this, you can use the route matcher to define which routes are protected and then call auth().protect() only for those matches. All other routes will remain public.

import { auth, clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';

const isPublicRoute = createRouteMatcher(['/sign-in(.*)', '/sign-up(.*)']);

export default clerkMiddleware((auth, request) => {
  // If the route is not public, then it is protected
  if (!isPublicRoute(request)) {
    auth().protect();
  }
});

export const config = {
  matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)'],
};

This example uses createRouteMatcher to define the public routes. The middleware logic is inverted: if the request doesn't match a public route, it calls auth().protect(), which enforces authentication. This gives you fine-grained control over your application's security model.

The Middleware Matcher

It's important to distinguish between Clerk's route matching and the Next.js middleware matcher config. The config object exported from middleware.ts tells Next.js when to run the middleware function at all.

This is a performance optimization. By excluding paths for static assets like images (_next/static), fonts, and favicons, you prevent the middleware from running unnecessarily on those requests. Clerk's logic, like publicRoutes or createRouteMatcher, only runs after the Next.js matcher has determined the middleware should execute.

The Next.js matcher is for performance. Clerk's tools are for security logic.

With these tools, you can build a robust and flexible authentication layer for any Next.js application. Now, let's test your understanding.

Quiz Questions 1/4

What is the primary function of the clerkMiddleware() in a Next.js application?

Quiz Questions 2/4

By default, without any configuration, how does clerkMiddleware() treat the routes in your application?

Properly configuring your middleware is the key to a secure and efficient application.