Skip to main content

AuthGuard Pattern: Usage, Implementation, and Best Practices

This document explains the consolidated AuthGuard pattern for authentication in your app, including:

  • Why this pattern is recommended
  • How to use the AuthGuard component and hooks
  • Implementation details
  • Example usage for layouts, pages, and conditional UI
  • Industry standards and rationale

Why Use a Centralized AuthGuard?

  • Single source of truth: All protected route logic (redirect, fallback, loading, inline sign-in) is in one place.
  • DRY: No need to repeat guard logic on every page/component.
  • Flexible: Supports redirect, fallback, or inline sign-in with simple props.
  • Clear separation: Use the AuthGuard component for protected routes, and the useAuthGuard hook for conditional UI.

AuthGuard Component: Implementation

import { useAuthQuery } from "@/lib/hooks/useAuthQuery"
import { useRouter } from "next/navigation"
import { useEffect } from "react"
import { Loader2 } from "lucide-react"
import SignIn from "./SignIn"

interface AuthGuardProps {
children: React.ReactNode
fallback?: React.ReactNode
redirectTo?: string
showSignIn?: boolean
loadingComponent?: React.ReactNode
}

export default function AuthGuard({
children,
fallback,
redirectTo = "/signin",
showSignIn = false,
loadingComponent,
}: AuthGuardProps) {
const { user, isLoading, isInitialized } = useAuthQuery()
const router = useRouter()

useEffect(() => {
if (isInitialized && !isLoading && !user && !fallback && !showSignIn) {
router.replace(redirectTo)
}
}, [user, isLoading, isInitialized, router, redirectTo, fallback, showSignIn])

if (!isInitialized || isLoading) {
if (loadingComponent) return <>{loadingComponent}</>
return (
<div className="flex items-center justify-center min-h-[300px]">
<div className="flex flex-col items-center space-y-4">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<p className="text-muted-foreground">Loading...</p>
</div>
</div>
)
}

if (user) return <>{children}</>
if (fallback) return <>{fallback}</>
if (showSignIn) return <SignIn />
return null
}

How to Use

1. Protect a Layout or Page

// app/profile/layout.tsx
import AuthGuard from "@/components/auth/AuthGuard"

export default function ProfileLayout({ children }) {
return <AuthGuard>{children}</AuthGuard>
}

2. Show Inline Sign-In Instead of Redirect

<AuthGuard showSignIn>{children}</AuthGuard>

3. Show a Fallback Component

<AuthGuard fallback={<div>Access denied</div>}>{children}</AuthGuard>

4. Custom Loading UI

<AuthGuard loadingComponent={<MySpinner />}>{children}</AuthGuard>

Conditional UI (No Redirect)

Use the useAuthGuard hook for conditional rendering in any component:

import { useAuthGuard } from "@/lib/hooks/useAuthGuard"

const { isAuthenticated, user } = useAuthGuard()
if (isAuthenticated) {
// Show user content
}

Industry Standards & Rationale

  • Centralized guard components are the standard in Next.js, React Router, and other frameworks for DRY, maintainable auth.
  • Hooks for state, components for side effects: Use hooks for state, components for redirect/fallback logic.
  • Flexible props: Allow for fallback, inline sign-in, and custom loading for better UX.
  • SSR/SSG: For server-rendered routes, check session on the server and use the same guard pattern on the client.

Summary Checklist

  • Use AuthGuard in all protected layouts/pages
  • Use useAuthGuard for conditional UI (no redirect)
  • Remove duplicate/legacy guard hooks
  • Pass fallback, showSignIn, or loadingComponent as needed
  • Redirects and toasts are handled in one place

This pattern keeps your authentication logic robust, DRY, and easy to reason about.