Aftershock Network
Aftershock · Answers

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:

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:

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:

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:

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:

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:

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:

Use a builder (Retool, Internal, Tooljet) when:

What it actually costs

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:

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 →