Admin Dashboard
Protected routes, role-based access control, sidebar navigation, and admin-only user and file management.
Overview
The dashboard is a protected area for authenticated users. It includes account settings, billing management, and, for admin users, user management and file management sections.
All dashboard routes live under app/dashboard/. The layout applies authentication protection and provides the shared sidebar, header, and breadcrumb navigation.
Directory Structure
app/dashboard/
├── layout.tsx # Auth check + sidebar shell
├── page.tsx # Dashboard home
├── loading.tsx # Suspense loading state
├── dashboard-content.tsx # Dashboard content component
├── billing/
│ ├── page.tsx # Subscription management
│ ├── billing-content.tsx # Billing UI component
│ ├── current-plan-card.tsx
│ └── loading.tsx
├── settings/
│ ├── page.tsx # Account settings
│ ├── settings-form.tsx
│ └── loading.tsx
├── profile/
│ ├── page.tsx # User profile
│ ├── profile-form.tsx
│ ├── loading.tsx
│ ├── change-email/
│ │ ├── page.tsx
│ │ ├── change-email-form.tsx
│ │ └── loading.tsx
│ └── change-password/
│ ├── page.tsx
│ ├── change-password-form.tsx
│ └── loading.tsx
└── (admin)/ # Admin-only routes
├── layout.tsx # Role check (admin only)
├── users/
│ ├── page.tsx # User management
│ └── loading.tsx
└── files/
├── page.tsx # File management
├── files-content.tsx
└── loading.tsxLayout and Authentication
The dashboard layout in app/dashboard/layout.tsx checks for a valid session on every request and redirects unauthenticated users to the sign-in page:
// app/dashboard/layout.tsx
export default async function DashboardLayout({ children }) {
const session = await getSession();
if (!session) redirect("/auth/sign-in");
return (
<SubscriptionProvider>
<SidebarProvider>
<DashboardSidebar session={session} />
<SidebarInset>
<header className="relative z-50 flex h-16 shrink-0 items-center border-b bg-background px-2 sm:px-6">
<SidebarTrigger className="-ml-1" />
<div className="hidden md:flex md:items-center">
<Separator orientation="vertical" className="mx-2 h-4" />
<DashboardBreadcrumb />
</div>
<DashboardMobileHeader user={session.user} />
</header>
<main className="flex flex-1 flex-col p-2 sm:p-6">{children}</main>
</SidebarInset>
</SidebarProvider>
</SubscriptionProvider>
);
}SubscriptionProvider wraps the layout so any dashboard component can access the user's current plan without an extra fetch. DashboardMobileHeader provides a responsive mobile navigation header.
Sidebar Navigation
The sidebar is built in components/dashboard/sidebar.tsx using shadcn/ui's Sidebar component. Navigation items are grouped into three sections:
| Group | Items |
|---|---|
| Main | Dashboard |
| Admin | Files, Users (hidden for non-admins) |
| Secondary | Billing, Settings |
The admin group renders conditionally based on the session role:
const isAdmin = session?.user?.role === "admin";
const navGroups = {
main: byGroup("main"),
admin: isAdmin ? byGroup("admin") : [], // Empty for non-admins
secondary: byGroup("secondary"),
};Non-admin users never see the Files or Users links. They are hidden at the component level, and the routes are also protected at the layout level (see below).
The sidebar footer shows the user's name, email, and current plan name. It includes a dropdown with links to the profile page, a theme toggle, and sign out.
Breadcrumbs
The DashboardBreadcrumb component in components/dashboard/breadcrumb.tsx generates breadcrumbs automatically from the current URL path. No manual configuration is needed. Adding a new route produces the correct breadcrumb trail.
Admin Routes and Role Protection
Admin-only routes live in the (admin) route group. The group has its own layout that checks the user's role before rendering children:
// app/dashboard/(admin)/layout.tsx
export default async function AdminLayout({ children }) {
const session = await getSession();
if (!session) redirect("/auth/sign-in");
if (session.user.role !== "admin") redirect("/dashboard");
return <>{children}</>;
}Any route under app/dashboard/(admin)/ requires the admin role at the server level. If someone navigates directly to /dashboard/users by URL, they are redirected to /dashboard unless they are an admin.
User Management
The user management page at /dashboard/users (app/dashboard/(admin)/users/page.tsx) lets admins:
- View all registered users with their email, name, verification status, role, ban status, join date, and last updated date
- Toggle user roles between
userandadmin - Ban and unban users
- Create new user accounts (sends a welcome email with the provided password)
- Reset a user's password (generates a secure password, copies to clipboard)
- Revoke all active sessions for a user
- Impersonate a user
- Delete users (requires typing
DELETEto confirm)
Most user operations (role changes, ban/unban, password reset, session revocation, impersonation, and deletion) run directly from the client via Better Auth's authClient.admin.* methods. Creating a user also triggers a sendWelcomeEmail Server Action to dispatch the welcome email.
File Management
The file management page at /dashboard/files (app/dashboard/(admin)/files/) lets admins browse and manage objects stored in Cloudflare R2:
- Browse folder structure
- View file names, sizes, and last modified dates
- Upload files directly to the current folder
- Create new folders
- Open files by URL in a new tab
- Copy the public URL of a file to clipboard
- Delete individual files or folders
- Toggle between list view and image preview (grid) view
File operations use the utilities in lib/server/s3.ts. See the File Uploads page for details on the storage layer.
Adding New Admin Pages
To add a new admin-only page:
- Create the page file inside
app/dashboard/(admin)/your-page/page.tsx - Add the corresponding nav item to
navigationItemsincomponents/dashboard/sidebar.tsxwithgroup: "admin"
The role check and breadcrumbs are handled automatically by the existing layout and breadcrumb component.
Adding New User-Facing Dashboard Pages
To add a page accessible to all authenticated users:
- Create the page file at
app/dashboard/your-page/page.tsx - Add the nav item to
navigationItemsincomponents/dashboard/sidebar.tsxwithgroup: "main"orgroup: "secondary"
The session check in the root dashboard layout protects the new page automatically.
How Next Starter sends transactional emails with SMTP2Go, including configuration, the sendEmail function, built-in HTML templates, disposable email blocking, and adding custom templates.
File Uploads
Upload files directly from the browser to Cloudflare R2 using presigned URLs. Includes server utilities, a client image pipeline, and an admin file manager.