The boilerplate provides a robust system for protecting routes and handling authentication using Next.js middleware and BetterAuth.
apps/web/src/middleware.ts
// apps/web/src/middleware.ts import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { getSessionCookie } from "better-auth/cookies"; export const config = { matcher: [ "/dashboard/:path*", "/settings/:path*", "/api/protected/:path*", ], }; export function middleware(request: NextRequest) { const sessionCookie = getSessionCookie(request); const isAuthenticated = sessionCookie !== null; if (!isAuthenticated) { return NextResponse.redirect(new URL("/login", request.url)); } return NextResponse.next(); }
apps/server/src/routers/index.ts
// apps/server/src/routers/index.ts import { protectedProcedure, publicProcedure, router } from "../lib/trpc"; export const appRouter = router({ // Public routes healthCheck: publicProcedure.query(() => { return { message: "OK" }; }), // Protected routes privateData: protectedProcedure.query(({ ctx }) => { return { message: "This is private", user: ctx.session.userId, }; }), });
// apps/web/src/app/dashboard/page.tsx import { authClient } from "@/lib/auth-client"; import { redirect } from "next/navigation"; export default async function DashboardPage() { const session = await authClient.getSession(); if (!session) { redirect("/login"); } return ( <div> <h1>Dashboard</h1> <p>Welcome {session.user.name}</p> </div> ); }
// apps/web/src/app/(protected)/layout.tsx import { authClient } from "@/lib/auth-client"; import { redirect } from "next/navigation"; export default async function ProtectedLayout({ children, }: { children: React.ReactNode; }) { const session = await authClient.getSession(); if (!session) { redirect("/login"); } return ( <div> <nav>{/* Navigation */}</nav> {children} </div> ); }
app/ ├── (auth)/ # Authentication routes │ ├── login/ │ ├── signup/ │ └── forgot-password/ │ ├── dashboard/ # Protected routes │ ├── about/ └── pricing/
// apps/web/src/components/protected-component.tsx "use client"; import { useAuth } from "@/hooks/use-auth"; import { redirect } from "next/navigation"; export function ProtectedComponent() { const { user, isLoading, isAuthenticated } = useAuth(); if (isLoading) { return <div>Loading...</div>; } if (!isAuthenticated) { redirect("/login"); } return ( <div> <h1>Protected Content</h1> <p>Welcome {user.name}</p> </div> ); }