How to Build an Admin Dashboard in React — Patterns That Actually Scale
A production-quality admin dashboard in React in 2026 is built on React 18+ with TypeScript, Vite or Next.js for build tooling, React Router for routing, TanStack Query for data, Tailwind for styling, TanStack Table for data tables, and an accessibility-focused component library (Radix UI, Headless UI, or shadcn/ui). The hard parts aren't the framework choices — those are commoditized at this point. The hard parts are: handling massive data tables without freezing the browser, implementing robust role-based access control, designing forms that don't lose user work on errors, and structuring the codebase so it doesn't become unmaintainable at 50+ pages.
This article walks through the patterns that actually work in production, drawn from Eden — the internal admin platform Aftershock Network builds and uses to run the entire operation.
Why custom React admin dashboards win over Retool / Internal / Tooljet
Low-code admin builders (Retool, Internal.io, Tooljet, Appsmith) are excellent for prototyping. They become painful at production scale for predictable reasons:
Visual editors hit ceilings. When the admin tool needs custom UX that doesn't match the platform's component library — drag-and-drop workflows, complex multi-step forms, custom data visualizations — you end up writing custom JavaScript inside the visual editor, which negates most of the speed advantage and creates code that's harder to maintain than a normal React app.
Vendor lock-in is real. The dashboard you built in Retool can't easily be exported and run somewhere else. If Retool changes pricing, has an outage, or you decide you want different capabilities, you start over.
Multi-user UX hits limits. Retool can serve a small operations team. When the admin tool has 30+ daily users with role-based access, mixed device types, and varied workflow needs, the visual builder's assumptions start to crack.
Custom integrations are painful. Connecting to internal APIs, custom auth systems, or unusual data sources works fine for the common cases. Edge cases require writing real code inside the platform's constraints.
The pattern that works in practice: use Retool for the first few weeks to validate the operation actually needs the tool, then commission a custom build when the prototype proves out.
The architecture
A well-structured React admin dashboard has these layers:
1. Routing and shell
React Router v6+ for client-side routing. A persistent shell component handles navigation, the sidebar, the user menu, and global notifications. Pages are lazy-loaded so the initial bundle stays small.
```tsx
<Routes>
<Route path="/" element={<Shell />}>
<Route index element={<Dashboard />} />
<Route path="users" element={<UsersList />} />
<Route path="users/:id" element={<UserDetail />} />
<Route path="settings" element={<Settings />} />
{/ ...more routes... /}
</Route>
<Route path="/login" element={<Login />} />
</Routes>
```
The Shell handles auth state and redirects to login when needed. Nested routes inherit auth checks naturally.
2. Authentication
For most production admin dashboards in 2026, use a managed identity provider:
- Clerk — best-in-class developer experience, generous free tier, strong React integration
- Auth0 — enterprise standard, slightly more complex setup
- Supabase Auth — good if you're already on Supabase for the backend
- AWS Cognito — if you're in AWS heavily and want native integration
Self-rolling JWT auth is fine for very simple cases but quickly becomes more work than it's worth. The amount of edge cases (password reset, email verification, MFA, social auth, session management, refresh token rotation) is substantial.
For internal admin tools specifically, SSO via Google Workspace or Microsoft Entra is the right default — your team is already authenticated; reuse it.
3. Authorization (RBAC)
Define explicit roles. Don't try to use string comparisons across the codebase.
```tsx
type Role = 'admin' | 'manager' | 'user' | 'viewer';
const ROLE_PERMISSIONS: Record<Role, Permission[]> = {
admin: ['*'],
manager: ['users.read', 'users.write', 'orders.read', 'orders.write'],
user: ['orders.read', 'orders.write'],
viewer: ['orders.read'],
};
```
Permission checks at the component level for UX:
```tsx
{hasPermission('users.delete') && <DeleteButton />}
```
But authorization MUST be enforced at the API layer. Hiding the button is not security. The backend must check the user's role on every request.
4. Data layer with TanStack Query
TanStack Query (formerly React Query) handles data fetching, caching, refetching, and optimistic updates. It's become the standard for production React applications.
```tsx
function UsersList() {
const { data, isLoading, error } = useQuery({
queryKey: ['users', { page, filter }],
queryFn: () => api.users.list({ page, filter }),
});
// ...render
}
```
Benefits over raw fetch + useState:
- Automatic caching and deduplication
- Background refetching on focus, network reconnect
- Optimistic updates with rollback
- Pagination and infinite scrolling primitives
- Built-in retry logic with exponential backoff
- Devtools for debugging
For mutations:
```tsx
const updateUser = useMutation({
mutationFn: (data) => api.users.update(userId, data),
onSuccess: () => queryClient.invalidateQueries(['users']),
});
```
5. Data tables
Most admin time is spent in tables. Get this right and the admin feels fast; get it wrong and the admin feels slow.
TanStack Table is the standard. It's headless (you bring your own styling) and handles sorting, filtering, pagination, column visibility, row selection, and grouping.
For tables with thousands of rows, virtualization is mandatory:
```tsx
const rowVirtualizer = useVirtualizer({
count: rows.length,
getScrollElement: () => scrollRef.current,
estimateSize: () => 48,
});
```
Server-side pagination beats virtualization for very large datasets — never load 100,000 rows into the browser. The API should support filtering, sorting, and pagination so the table shows a window of the data based on the user's current view.
Features that make tables actually useful:
- Sticky header so column names stay visible while scrolling
- Sticky first column for wide tables
- Column visibility toggles
- Column width persistence across sessions
- Bulk row selection with shift-click ranges
- Bulk actions on selected rows
- Quick filters at the top (status, date range, tag)
- Full-text search across all columns
- Keyboard navigation (arrow keys, enter to open detail)
- Export to CSV
6. Forms
React Hook Form + Zod for validation is the standard combination in 2026.
```tsx
const schema = z.object({
email: z.string().email(),
name: z.string().min(1),
role: z.enum(['admin', 'user']),
});
function UserForm() {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
});
// ...render
}
```
The things that separate good forms from bad forms:
- Field-level validation with helpful error messages
- Form-level validation for cross-field rules
- Disable submit button during submission, show loading state
- Preserve user input on validation errors (don't reset the form)
- Auto-save drafts for long forms (using localStorage or a backend draft endpoint)
- Confirm before navigating away from a dirty form
- Show what changed when editing (highlight modified fields)
- Sensible default focus management
7. Real-time updates
Three patterns, ordered by complexity:
Polling with TanStack Query — refetchInterval set to 5-30 seconds. Simple, reliable, handles disconnections gracefully. Right for most admin scenarios.
Server-Sent Events (SSE) — one-way push from server to client. Good for live dashboards where data changes frequently. Easier than WebSockets.
WebSockets — bidirectional, for highly interactive scenarios like multi-user collaboration or live chat. Overkill for most admin use cases.
For Eden, our internal admin, polling at 10-30 second intervals handles 90% of "real-time" needs. WebSockets are reserved for specific features (live game multiplayer in Aftershock Games, real-time fight scoring in Fighter Management).
8. Component library
You can build everything from scratch with Tailwind, but using a primitive library accelerates significantly:
- Radix UI — unstyled, accessible primitives (dialog, dropdown, tabs, tooltip)
- Headless UI — Tailwind-team-built primitives
- shadcn/ui — copy-paste Radix-based components with Tailwind styling
- Mantine — fuller-featured component library with styling included
For Eden, we use shadcn/ui patterns — copy components into the project, customize them to match our design system, own the code. This avoids the dependency-on-vendor pattern of fuller component libraries while still getting the speed of pre-built primitives.
9. State management
For most admin dashboards, TanStack Query + URL state + small amounts of useState handles all the state needs.
When you need global client state (the user object, theme preference, sidebar collapsed state), Zustand is the best lightweight option. Redux is overkill for most admin applications in 2026.
10. Type safety
TypeScript end-to-end. Backend types should match frontend types — use a code-gen tool (tRPC, OpenAPI generators, custom code-gen) or share types via a monorepo. Never define API response types twice.
The Eden architecture
Eden is the internal admin platform Aftershock Network builds and runs. It's a real-world example of this architecture at production scale:
Stack: React 18 with TypeScript, Vite for build tooling, React Router v6 for routing, TanStack Query for data, Tailwind CSS for styling, custom components with shadcn/ui patterns.
Scope: Client management, project tracking, quote generation, internal product catalog, operator agreement tracking, billing integration, security vault, fighter management (separate sub-app for combat sports operations), content engine (the platform this article is being published from), growth engine, and more.
Scale: ~50 pages, 30+ database tables, integrated with Stripe (billing), Resend (email), Telnyx (SMS), Square (payments), GitHub (source control), and several internal APIs.
Patterns that hold up:
- Tables everywhere, all virtualized for large datasets
- Forms with Zod validation
- Polling-based "real-time" for most live data
- Custom components evolved from shadcn/ui patterns
- Strict TypeScript across frontend + backend
- Role-based access control with the platform admin (Eric) seeing everything
Eden didn't ship as a monolith — it grew incrementally as Aftershock Network's operational needs evolved. Each new module shipped in 2-6 weeks of focused development.
When custom React admin makes sense
Build custom when:
- The operation has unusual workflows that don't fit a generic admin builder
- You expect 10+ daily users across multiple roles
- The dashboard needs deep integration with custom backend logic
- You're at 6+ months of operating on Retool / Internal and hitting walls
- The admin tool is strategic to the business (your operators live in it)
Use a builder (Retool, Internal, Tooljet) when:
- You need something working tomorrow
- The operation is small (under 5 users)
- The workflows are simple and fit the builder's patterns
- You're still validating whether the tool is needed at all
What it actually costs
- Focused single-area admin dashboard (one operational domain): $25,000-$60,000, 4-8 weeks
- Multi-area admin platform (covering most of a business): $80,000-$200,000+, 12-20 weeks
- Ongoing maintenance and feature work: $3,000-$10,000/month depending on velocity
The math is more favorable than it sounds. A 15-person operations team using a custom admin for 3 hours/day at $50/hour blended cost is $164,000/year in operator time. Saving 20% of that time with a fitted tool is $32,800/year — which justifies a $60,000 build in about 22 months.
When upfront cost is the constraint
A serious custom admin dashboard is real money. Aftershock Network's Operator Model structures the engagement with a small down payment and monthly installments over an agreed term, with the dashboard shipping in phases so your team is using the new tool while you're still paying off the development.
More about the Operator Model →
How to start
If you're seriously evaluating a custom admin dashboard:
- Currently on Retool / Internal / Tooljet and hitting walls: time to evaluate custom. Start with a scoping call to identify what's broken and what would replace it.
- No admin tool yet, operating in spreadsheets and direct database queries: probably skip Retool entirely and go straight to custom if the operation is going to scale. Start with the highest-pain workflow.
- Custom admin exists but is 5+ years old and slow: rebuild is usually faster than refactoring at that age. Plan a 12-20 week ground-up rebuild on the modern stack.
Every Aftershock Network admin dashboard engagement starts with a real conversation about your operation, the workflows that need tooling support, and the team that will actually live inside the dashboard daily.
Frequently asked questions
Should I use Retool or build a custom React admin dashboard?
Retool, Internal, Tooljet, and Appsmith are great for prototyping internal tools and for simple admin needs. They become painful when the operation gets complex — when you need custom workflows, deep integration with your business logic, real-time data, or substantial volume across many users. Custom-built React admin dashboards take longer to start but scale further without hitting the platform's ceilings. Pattern that works: prototype on Retool, validate the operation needs the tool, then commission a custom build when the prototype outgrows Retool's constraints.
What stack should I use to build a React admin dashboard in 2026?
A common modern stack: React 18+ with TypeScript, Vite for build tooling, React Router for routing, TanStack Query for data fetching and caching, Tailwind CSS for styling, Radix UI or Headless UI for accessible primitives, TanStack Table for data tables, React Hook Form for forms, Zod for validation. Backend can be anything — Node/Express, Django, Rails, Go, whatever fits the team. Authentication via Clerk, Auth0, Supabase Auth, or custom JWT.
How long does it take to build a production-quality admin dashboard?
A focused admin dashboard covering one operational area (customer management, order processing, content moderation, etc.) typically takes 4-8 weeks. A multi-area admin platform covering most of a business's operations takes 12-20 weeks for the initial build. Eden, the internal admin platform Aftershock Network ships and uses, has been built over multiple development cycles spanning ~30 weeks of focused engineering — covering everything from client management to fighter management to content publishing.
What are the most important features of an admin dashboard?
Most important: fast, filterable, sortable, paginated data tables (90% of admin time is spent in tables). Reliable forms with validation and good error handling. Role-based access control (different users see different things). Audit logging (who did what when). Search across the operation. Bulk actions on selected records. For ops-heavy roles, also: real-time data updates, keyboard navigation, command palette, dark mode, mobile responsiveness.
How do you handle authentication and authorization in an admin dashboard?
For authentication: use a managed identity provider (Clerk, Auth0, Supabase Auth) unless you have a specific reason to roll your own. Most production admin dashboards use OAuth + SSO with the company's identity provider (Google Workspace, Microsoft Entra, Okta). For authorization: implement RBAC with explicit role definitions (admin, manager, viewer, etc.) and enforce permissions at the API layer, not just the UI layer. Hiding a button in the UI is not security — the backend must enforce the same rules.
How do you handle data tables that need to display thousands of rows?
Virtualization is the answer at scale. TanStack Virtual or React Virtuoso renders only the rows currently visible in the viewport, with smooth scrolling through massive datasets. Pair virtualization with server-side pagination and filtering — never load 100,000 rows into the browser even with virtualization. The database query layer should support filtering, sorting, and pagination natively so the API returns 50-200 rows per request based on the user's current view.
What about real-time data updates in admin dashboards?
Three common patterns: polling (TanStack Query refetches every N seconds), Server-Sent Events (one-way updates from server to client), and WebSockets (bidirectional, for highly interactive use cases). For most admin dashboards, polling with 5-30 second intervals is sufficient — simple, reliable, and handles disconnections gracefully. SSE works well for live dashboards. WebSockets are necessary for operations like multi-user collaborative editing or live chat — overkill for most admin scenarios.
Related answers
Need a real admin dashboard, not a Retool patch?
Aftershock Network builds custom admin dashboards in React for operators who need real internal tools — Eden, our own admin platform, runs the entire operation. Tell us what you're managing and we'll show you what a proper internal tool looks like.
Start a conversation →