Recipe: Add a protected page
Recipe: Add a protected page
There are two layers to wire (see architecture.md for why both exist) — skipping the second one is the classic mistake, since the first layer alone is not real authorization.
-
Create the page under the right route group:
- Any signed-in user →
app/(app)/your-page/page.tsx - Paid/trialing users only → same group, call
requireAccess() - Admins only →
app/(admin)/admin/your-page/page.tsx
- Any signed-in user →
-
Call the matching DAL function at the top of the page (or its layout, if every page under it needs the same gate):
import { verifySession } from "@/lib/dal"; // any signed-in user // or: import { requireAccess } from "@/lib/dal"; // paid tier // or: import { requireAdmin } from "@/lib/dal"; // admin only export default async function YourPage() { const { user } = await verifySession(); // ... }This is the real check — it runs server-side on every request and redirects (
verifySession/requireAccess) or 404s (requireAdmin) before any of your page's logic runs. -
Add the path prefix to
proxy.ts— this is only a fast, cookie-based redirect for UX (avoids a round-trip to render a page that will just redirect), so it can lag slightly, but keep it in sync:const PROTECTED_PREFIXES = [ "/dashboard", "/billing", "/settings", "/subscribe", "/admin", "/your-page", // add this ];and add the matching entry to the
config.matcherarray below it. -
Verify: log out and visit the URL directly — you should land on
/logininstantly (proxy) even before hitting the DAL check.
Server Actions need the same treatment: call verifySession() /
requireAccess() / requireAdmin() as the first line of the action function
itself — Server Actions aren't covered by proxy.ts at all.