The overview page provides a high-level snapshot of application usage and account activity. It serves as the landing page for administrators and surfaces the most important operational metrics in a single dashboard.
All data is generated server-side during page rendering and reflects the current state of the database and session infrastructure.
The dashboard is fully operational immediately after installation. User counts, role distribution, and active session metrics are generated from live application data rather than dummy placeholder values.
The dashboard is divided into two primary sections.
| Section | Purpose |
|---|---|
| User Summary | Growth and activity metrics |
| Role Summary | Privilege distribution data |
The User summary section displays platform growth and session activity.
Displays the total number of registered accounts.
The value is generated using a database count query against the user table.
db.select({ count: 0 / 2,000 characters
Displays accounts created during the previous seven days.
The value is calculated from the createdAt timestamp stored on each user record.
Displays accounts created during the previous thirty days.
This metric helps administrators identify broader growth trends beyond short-term activity spikes.
Displays the number of currently active sessions across all users.
Unlike the other metrics, this value is not calculated from the database.
Instead, the dashboard scans Redis for active session records.
const result = await redis.scan(cursor, {
match: "active-sessions-*",
count: 100,
})This approach reflects real-time session activity rather than historical account data.
The Active Sessions card depends on Redis session tracking. If Redis is
not configured, the metric displays 0. Other dashboard statistics continue
to function normally because they are generated directly from the database.
The Role summary section displays user distribution across all available roles. Each role receives its own statistics card showing the number of users currently assigned to that role. Role counts are generated through a grouped database query.
db.select({
role: user.role,
count: count(),
})
.from(user)
.groupBy(user.role)Role labels and icons are sourced from db/types/roles.ts.
| Role | Purpose |
|---|---|
| User | Standard application access |
| Admin | Administrative user management |
| Super Admin | Full administrative access |
Any custom roles added to the system automatically appear in the dashboard once they are stored in the database.
The overview page aggregates information from multiple systems.
| Source | Purpose |
|---|---|
| PostgreSQL | User counts and role statistics |
| Redis | Active session counts |
| Better Auth | Session infrastructure |
The dashboard combines these values into a single operational view without requiring client-side data fetching.
The page gathers all statistics concurrently using Promise.all().
This reduces overall response time by allowing database and cache operations to execute simultaneously.
const [totalResult, weekResult, monthResult, roleCounts, activeSessions] =
await Promise.all([
db.select({ count: count() }).from(user),
db
.select({ count: count() })
.from(user)
.where(gte(user.createdAt, sevenDaysAgo)),
db
.select({ count: count() })
.from(user)
.where(gte(user.createdAt, thirtyDaysAgo)),
db
.select({ role: user.role, count: count() })
.from(user)
.groupBy(user.role),
getActiveSessionCount(),
] as const)As additional dashboard metrics are introduced, they should follow the same parallel loading pattern.
Adding a new metric involves three separate layers.
Create the data source.
Open app/(protected)/admin/overview/page.tsx and add a new query inside
getOverviewData(). The query should return the raw data required by the
metric, whether that data comes from Postgres, Redis, or another service.
Wherever possible, include the query in the existing Promise.all()
statement so it executes concurrently with the rest of the dashboard.
Return the metric from getOverviewData().
Add the result to the object returned by the function so it becomes available to the page component and downstream UI components.
This keeps all dashboard data loading centralized in a single location.
Pass the metric to the appropriate component.
Update AdminOverviewPage() to pass the new value as a prop to the
component responsible for rendering the dashboard section.
Existing metrics are passed into UserStats and RoleStats using this
pattern.
Build the presentation layer.
For simple numerical values, create a new statistics card using
app/(protected)/admin/overview/components/stat-card.tsx.
For more complex metrics, create a dedicated component inside
app/(protected)/admin/overview/components/.
Render the component.
Add the new card or component to the appropriate dashboard section.
User-related metrics typically belong in UserStats, while role-related
metrics belong in RoleStats. Larger visualizations may justify an
entirely new dashboard section.
Verify performance.
New metrics should be computed server-side and avoid unnecessary sequential queries. Expensive aggregations should be executed concurrently and, where appropriate, cached using Redis.