Protect pages in Next.js with middleware — step by step
Use Next.js middleware to protect private pages like /dashboard. Redirect unauthenticated users to sign-in, refresh sessions, and handle role-based access. Full code included.
What is middleware?
Middleware is code that runs before a page loads. When someone visits /dashboard, middleware checks if they're logged in. If yes, the page loads normally. If no, they get redirected to /signin. It runs on the server (Edge), so it's fast and the user never sees the protected page.
Step 1: Create middleware.ts
Create a file called middleware.ts in your project root (same level as app/, not inside it):
// middleware.ts
import { NextResponse, type NextRequest } from "next/server";
import { createServerClient } from "@supabase/ssr";
export async function middleware(request: NextRequest) {
let response = NextResponse.next({
request: { headers: request.headers },
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookies) {
cookies.forEach(({ name, value, options }) => {
response.cookies.set(name, value, options);
});
},
},
}
);
// Refresh the session (important!)
const { data: { user } } = await supabase.auth.getUser();
// If not logged in and trying to access /dashboard → redirect
if (!user && request.nextUrl.pathname.startsWith("/dashboard")) {
const url = request.nextUrl.clone();
url.pathname = "/signin";
return NextResponse.redirect(url);
}
return response;
}Step 2: Tell middleware which paths to run on
Add a config export at the bottom of the same file. This tells Next.js which routes should trigger the middleware:
// Add at the bottom of middleware.ts
export const config = {
matcher: [
"/dashboard/:path*",
"/docs/:path*",
],
};Now middleware only runs for /dashboard and /docs pages. Other pages (homepage, blog) load without any check — which is what you want for public pages.
Step 3: Test it
Start your dev server (npm run dev). Open http://localhost:3000/dashboard without being logged in. You should be redirected to /signin. Log in, then try again — you should see the dashboard.
Why refresh the session in middleware?
Supabase uses JWT tokens that expire. If you don't refresh them, users get logged out randomly. Calling supabase.auth.getUser() in middleware refreshes the token on every page visit, so the session stays alive. This is the recommended pattern from Supabase.
Middleware already set up
In Delfy, middleware handles session refresh and route protection out of the box. Dashboard and docs are protected, public pages are open. No setup needed.
Get the boilerplate