We just raised our biggest updates 🎉 - Nextadmin V2.0

NextAdmin Logo

Project Architecture

NextAdmin follows a modular, domain-driven architecture that separates concerns and scales cleanly as your application grows. The codebase is organized so both developers and AI coding agents can navigate it intuitively.

Directory structure

src/
├── app/                    # Next.js App Router
│   ├── layout.tsx          # Root layout (ThemeProvider + Providers + Toaster)
│   ├── providers.tsx       # Client wrapper for contexts
│   ├── (with-layouts)/     # Pages WITH app shell (sidebar + header)
│   │   ├── layout.tsx      # App shell: collapsible sidebar + mobile sheet
│   │   ├── (dashboard)/    # Dashboard pages: Home / analytics / CRM / etc.
│   │   └── ...
│   └── (without-layouts)/  # Pages WITHOUT app shell
├── components/
│   ├── common/             # Shared UI Components: Header, Sidebar etc.
│   └── tailgrids/          # Design-System primitives (Button, Card, Sheet, Dialog, …)
├── services/               # Manage services
│   ├── api/                # Mock REST layer
│   │   ├── home/
│   │   └── ...
├── hooks/                  # Hooks
├── types/                  # Shared TypeScript types
├── utils/                  # Helper functions
│   ├── cn.ts
│   └── ...

Key Directories

  • app/ - Next.js App Router
    • (with-layouts)/ - Pages WITH app shell (sidebar + header)
    • (without-layouts)/ - Pages WITHOUT app shell
  • components/ - Shared UI Components
  • services/api/ - Mock REST layer with domain-specific data and types
  • hooks/ - Hooks
  • utils/ - Helper functions
  • types/ - Shared TypeScript types

API Layer

All the API calls are made through service functions located under services/api/<page-name>/. Each domain under services/api/<page-name>/ follows the same shape:

services/api/<page-name>/
├── data.ts     # Raw mock data (Optionally added for mock data source)
├── index.ts    # API requests
└── types.ts    # Request / Response types

Architectural patterns

1. App shell

The application has two layout groups in app directory: (with-layouts) and (without-layouts).

  • /app/(with-layouts)/layout.tsx has max-w-384 (≈1536px) shell width with a distinctive "card within a card" framing for page content. Both desktop sidebar and mobile Sheet are rendered at once, with one hidden via breakpoint.
  • /app/(without-layouts)/layout.tsx has no shell width and expands for full page content.

2. Co-located Sub-components

A page and it's sub-components are isolated into same place.

<page-name>/
├── page.tsx                       # Route entry
├── _component/                    # Components
│   └── <feature>/                 # A single component
│       ├── index.tsx              # Main Component Export
│       ├── <sub-component>.tsx    # Sub Component
│       ├── icons.tsx              # Icons required for the component
│       ├── skeletons.tsx          # Skeletons required for the component
│       ├── types.ts               # Types for the component
│       └── utils.ts               # Optional for utils
Any sub-component can also have sub-components following the same pattern, sub-component folders can be nested inside each other.

3. Mock API as a service layer

Next Admin is designed to be integrated with any backend. Current implementation uses a mock REST layer which looks something like this:

// services/api/manage-team/index.ts
export async function getTeamMembers(): Promise<TeamMember[]> {
  await delay();
  return teamMembers;
}

But it can easily be replaced with a real API calls using fetch or axios.

// services/api/manage-team/index.ts
export async function getTeamMembers(): Promise<TeamMember[]> {
  const { data } = await axios.get("/api/manage-team");
  return data;
}

/api-integration skill can be used to replace the mock API with a real API.

4. Tanstack Query and response mapping

API calls are made using Tanstack Query and gets mapped to the expected shape before rendering, so that you or your AI Agent can't mess up with the UI. The data flow is as follows:

services/api/<domain>  →  useQuery hook in component  →  response mapper  →  rendered UI

Example:

// services/api/manage-team/index.ts
export async function getTeamMembers(): Promise<TeamMember[]> {
  await delay();
  return teamMembers;
}

// _component/manage-team/utils.ts
export async function getTeamMembersResponseMapper(teamMembers: TeamMember[]): Promise<TeamMemberResponse[]> {
  return teamMembers.map((teamMember) => ({
    id: teamMember.id,
    name: teamMember.name,
    email: teamMember.email,
    role: teamMember.role,
  }));
}

// _component/manage-team/index.tsx
export default function ManageTeam() {
  const { data, isLoading } = useQuery({
    queryKey: ["manage-team"],
    queryFn: getTeamMembers,
  });

  if (isLoading) {
    return <ManageTeamSkeleton />;
  }

  const teamMembers = data ? getTeamMembersResponseMapper(data) : undefined;

  // ...
}

On this page

Tailgrids Pro

Access 600+ blocks, components and templates - built for speed, consistency, and scale.