Limited offer: $250 off for the first 200 customers!Claim discount

Add a private route

Create a page that only authenticated (and optionally paying) users can access.

Auth-protected pages — how it works

Every file under src/app/dashboard/ is automatically protected. src/app/dashboard/layout.tsx checks the Supabase session server-side and redirects any unauthenticated visitor to /signin. You don't need to add guards yourself.

1. Create the page

// src/app/dashboard/my-feature/page.tsx
import { createClient } from "@/libs/supabase/server";
import { redirect } from "next/navigation";

export default async function MyFeaturePage() {
  const supabase = await createClient();

  // Get the authenticated user
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) redirect("/signin");

  // Fetch their profile
  const { data: profile } = await supabase
    .from("profiles")
    .select("name, has_access")
    .eq("id", user.id)
    .single();

  return (
    <main className="p-8">
      <h1 className="text-2xl font-bold">Welcome, {profile?.name ?? "there"}</h1>
      {profile?.has_access ? (
        <p>You have full access.</p>
      ) : (
        <p>Upgrade to unlock this feature.</p>
      )}
    </main>
  );
}

Your page is now live at /dashboard/my-feature. Signed-out visitors are redirected to /signin automatically.

2. Gate behind a paid plan

Check has_access and redirect free users to the pricing page:

if (!profile?.has_access) {
  redirect("/#pricing");
}

3. Add a link in the dashboard nav (optional)

Open src/app/dashboard/page.tsx and add a link to your new page so users can discover it from the main dashboard view.

TipKeep business logic and data fetching in Server Components (no "use client"). Push interactivity to small Client Component leaves. This keeps your app fast and your secrets server-side.