16 KiB
//[doc-seo]
{
"Description": "Learn how to build modern web applications with ABP using React UI — a React-first approach with a dedicated Admin Console, built on Vite, shadcn/ui, Zod, and Axios."
}
React UI
Introduction
ABP provides a React UI option for building modern, client-side web applications. The React UI is part of the modern template system and is only available when creating a solution with the --modern flag via the ABP CLI or through the Modern Wizard in ABP Studio.
React UI is not available in the legacy (non-modern) templates. You must use the modern template system to get a React-based solution. See Creating a Solution below.
The React UI is built on a modern, industry-standard stack:
| Technology | Purpose |
|---|---|
| Vite | Build tool and dev server |
| React | UI framework |
| shadcn/ui | Component library (built on Radix UI + Tailwind CSS) |
| Zod | Schema validation |
| Axios | HTTP client |
| Vitest | Unit testing |
| React Router | Client-side routing |
| OpenID Connect / OIDC | Authentication (via the ABP Auth Server) |
React App and Admin Console
Every modern React solution consists of two parts: your React application and the ABP Admin Console.
React App (Your Application)
This is your application — the user-facing SPA that you own and customize freely. It comes with:
- A sample Books CRUD page (when
--sample-crud-pageis used) demonstrating how to build a full create/read/update/delete page with the ABP backend - A plain Users page as a minimal reference
- Pre-configured authentication via OIDC against the ABP Auth Server
- Pre-configured HTTP client (Axios) with ABP API integration
This is where you build your business-specific pages and features.
The location of the React app differs by template type:
- Layered (
app --modern) and Single-layer (app-nolayers --modern): the React app lives at the root of the solution (alongside the backend projects). - Microservice (
microservice --modern): the React app lives atapps/react/.
React Admin Console (Volo.Abp.AdminConsole)
The ABP Admin Console is a pre-built React application that provides all standard ABP module management pages. It is delivered via the Volo.Abp.AdminConsole NuGet package and requires no modification on your part — it is updated automatically when you update your ABP packages.
The Admin Console includes pages for:
- Users & Roles management
- Organization Units
- Settings
- Audit Logs
- OpenIddict (Application, Scope management)
- Language Management (if included)
- Text Template Management (if included)
- GDPR (if included)
- SaaS / Tenant Management (if included)
- And all other optional module pages based on your solution configuration
How the Admin Console is hosted also differs by template type:
- Layered and Single-layer templates: The
Volo.Abp.AdminConsolepackage is added directly to your*.HttpApi.Hostproject. It serves the Admin Console React app at the/admin-console/*path of your backend (e.g.,https://localhost:44300/admin-console/). There is no separateapps/react-admin-console/folder — the Admin Console UI is embedded in and served by the backend. - Microservice template: The Admin Console runs as a standalone React app at
apps/react-admin-console/, served through the Web Gateway (YARP) alongside the main React app.
In both cases the Admin Console is accessible from the main React app via a navigation link.
Creating a Solution
Using ABP CLI
Pass the --modern flag to abp new:
# Layered app with React UI (default when --modern is used)
abp new Acme.BookStore --template app --modern
# Single-layer app with React UI
abp new Acme.BookStore --template app-nolayers --modern
# Microservice solution with React UI + React Admin Console
abp new Acme.BookStore --template microservice --modern
The react UI framework is the default when --modern is specified. You can also pass it explicitly:
abp new Acme.BookStore --template app --modern --ui-framework react
To create a solution without any UI (API-only backend):
abp new Acme.BookStore --template app --modern --ui-framework no-ui
See the ABP CLI documentation for the full list of options.
Using ABP Studio
Open ABP Studio and use the New Solution wizard. Select the Modern template variant and choose React as the UI framework. The wizard guides you through all available options and generates the solution.
Solution Structure
The layout of the React-related files depends on the template type.
Layered and Single-layer Templates
For app --modern and app-nolayers --modern, the React app lives at the root of the solution alongside the backend projects. The Admin Console is embedded in the backend via the Volo.Abp.AdminConsole NuGet package — there is no separate React Admin Console folder.
Acme.BookStore/
├── react/ # Your React application
│ ├── src/
│ │ ├── pages/ # Your page components
│ │ │ ├── books/ # Sample Books CRUD page
│ │ │ └── users/ # Sample Users page
│ │ ├── components/ # Shared UI components
│ │ ├── lib/ # Utilities, API clients
│ │ ├── hooks/ # Custom React hooks
│ │ └── main.tsx # Entry point
│ ├── public/
│ │ └── dynamic-env.json # Runtime configuration
│ ├── package.json
│ └── vite.config.ts
├── src/
│ ├── Acme.BookStore.Application/
│ ├── Acme.BookStore.Domain/
│ ├── Acme.BookStore.EntityFrameworkCore/
│ └── Acme.BookStore.HttpApi.Host/ # Hosts Admin Console at /admin-console/*
└── ...
Microservice Template
For microservice --modern, both the React app and the React Admin Console are standalone apps under the apps/ directory, each served through the Web Gateway (YARP).
Acme.BookStore/
├── apps/
│ ├── react/ # Your React application
│ │ ├── src/
│ │ │ ├── pages/
│ │ │ ├── components/
│ │ │ ├── lib/
│ │ │ ├── hooks/
│ │ │ └── main.tsx
│ │ ├── public/
│ │ │ └── dynamic-env.json # Runtime configuration
│ │ ├── package.json
│ │ └── vite.config.ts
│ ├── react-admin-console/ # ABP Admin Console (managed by ABP)
│ │ ├── public/
│ │ │ └── dynamic-env.json
│ │ └── ...
│ └── auth-server/ # OpenIddict Auth Server
├── gateways/
│ └── web/ # YARP reverse proxy for React apps
├── services/
│ ├── identity/
│ ├── administration/
│ └── ...
└── ...
Configuration
Runtime Configuration (dynamic-env.json)
The React app reads its runtime configuration from public/dynamic-env.json. This file is loaded at startup and allows you to change settings without rebuilding the application — useful for different environments (development, staging, production).
{
"oAuthConfig": {
"issuer": "https://localhost:44301/",
"clientId": "Acme.BookStore_App",
"scope": "openid profile email offline_access BookStore"
},
"apis": {
"default": {
"url": "https://localhost:44300",
"rootNamespace": "Acme.BookStore"
}
}
}
| Key | Description |
|---|---|
oAuthConfig.issuer |
URL of the OpenIddict Auth Server |
oAuthConfig.clientId |
The OpenIddict client ID registered for this app |
oAuthConfig.scope |
OAuth scopes to request |
apis.default.url |
Base URL of the backend API |
apis.default.rootNamespace |
Root namespace used for API proxy generation |
For the microservice modern template, the apis.default.url points to the Web Gateway (YARP reverse proxy) instead of a single backend host.
Admin Console Configuration
For the microservice template, the Admin Console has its own dynamic-env.json (at apps/react-admin-console/public/dynamic-env.json) and uses a separate OpenIddict client (<ProjectName>_AdminConsole).
For layered and single-layer templates, the Admin Console is embedded in the backend via the Volo.Abp.AdminConsole package and does not have a separate configuration file — it inherits its settings from the backend host.
Authentication
Both the React app and the Admin Console authenticate using OpenID Connect (OIDC) against the ABP Auth Server (OpenIddict). The auth flow is handled transparently — when a user visits a protected page, they are redirected to the Auth Server login page and returned to the app after successful authentication. All clients use the Authorization Code flow with PKCE, which is the recommended flow for SPAs.
The number of seeded OpenIddict clients depends on the template:
- Layered and Single-layer templates: one client is seeded —
<ProjectName>_Appfor the React SPA. The Admin Console is embedded in the backend and shares the same authentication context. - Microservice template: two clients are seeded —
<ProjectName>_Appfor the main React SPA and<ProjectName>_AdminConsolefor the standalone React Admin Console app.
Making API Calls
The React app uses Axios as the HTTP client, pre-configured with:
- The base URL from
dynamic-env.json - Automatic Bearer token injection from the OIDC session
- ABP-compatible error handling (reads
error.datafrom ABP error responses)
Example: Fetching a list of books
import { useQuery } from '@tanstack/react-query';
import { apiClient } from '@/lib/api-client';
interface BookDto {
id: string;
name: string;
type: number;
publishDate: string;
price: number;
}
interface PagedResult<T> {
items: T[];
totalCount: number;
}
export function useBooks() {
return useQuery({
queryKey: ['books'],
queryFn: () =>
apiClient
.get<PagedResult<BookDto>>('/api/app/book')
.then((res) => res.data),
});
}
Localization
The React app integrates with ABP's localization system. Localization resources are fetched from the backend at startup via the /api/abp/application-configuration endpoint and made available throughout the app.
Example: Using localization in a component
import { useLocalization } from '@/hooks/use-localization';
export function MyComponent() {
const { l } = useLocalization('BookStore');
return <h1>{l('Books')}</h1>;
}
The l(key) function looks up the key in the loaded localization resources for the given resource name ('BookStore' in this example).
Authorization
Permission checks are available via a hook that reads the current user's granted permissions from the ABP application configuration:
import { usePermissions } from '@/hooks/use-permissions';
export function BooksPage() {
const { isGranted } = usePermissions();
return (
<div>
{isGranted('BookStore.Books.Create') && (
<button>Create Book</button>
)}
</div>
);
}
Permissions are defined on the server side using ABP's permission system and are automatically available in the React app.
Tech Stack Details
shadcn/ui Components
The React app uses shadcn/ui as the component library. shadcn/ui is not a traditional npm package — components are copied directly into your project under src/components/ui/, giving you full ownership and the ability to customize them freely.
Common components available out of the box include: Button, Input, Table, Dialog, Form, Select, Tabs, Card, Badge, Dropdown Menu, and more.
Form Validation with Zod
Forms use Zod schemas for validation, integrated with React Hook Form:
import { z } from 'zod';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
const createBookSchema = z.object({
name: z.string().min(1).max(128),
price: z.number().min(0),
publishDate: z.string(),
});
type CreateBookInput = z.infer<typeof createBookSchema>;
export function CreateBookForm() {
const form = useForm<CreateBookInput>({
resolver: zodResolver(createBookSchema),
});
// ...
}
Testing with Vitest
The React app is pre-configured with Vitest for unit testing:
# Run tests
npm run test
# Run tests with coverage
npm run test:coverage
Running the Application
Development
- Start the backend (from ABP Studio or
dotnet runin the*.HttpApi.Hostproject). - Navigate to the React app directory and start the dev server.
For layered and single-layer templates, the React app is at the solution root:
cd react
npm install
npm run dev
For the microservice template, it is under apps/:
cd apps/react
npm install
npm run dev
The app will be available at https://localhost:3000 (or the port configured in vite.config.ts).
- Access the Admin Console:
- Layered / Single-layer: navigate to the backend URL +
/admin-console/(e.g.,https://localhost:44300/admin-console/). - Microservice: navigate to the Web Gateway URL +
/admin-console/.
- Layered / Single-layer: navigate to the backend URL +
Production Build
# Layered / Single-layer
cd react && npm run build
# Microservice
cd apps/react && npm run build
The output is placed in dist/ and can be served by any static file host or CDN.
Accessing the Admin Console
The Admin Console is accessible from within the main React app. After logging in, you will find a link to the Admin Console in the navigation. Clicking it opens the Admin Console, which is served by the backend at /admin-console/*.
The Admin Console provides full management capabilities for:
- Identity: Users, Roles, Claims, Organization Units
- OpenIddict: Applications, Scopes
- Settings: Application-wide and tenant-level settings
- Audit Logs: View and filter audit log entries
- Language Management (optional)
- Text Template Management (optional)
- GDPR (optional)
- SaaS / Tenant Management (optional)
- And more, depending on the modules included in your solution
Customization
Adding New Pages
Create a new file under src/pages/ and register the route in the router configuration:
// src/router.tsx (or similar)
import { BooksPage } from './pages/books/books-page';
import { MyNewPage } from './pages/my-feature/my-new-page';
const routes = [
{ path: '/books', element: <BooksPage /> },
{ path: '/my-feature', element: <MyNewPage /> },
// ...
];
Adding Menu Items
Add entries to the navigation configuration to include your new pages in the sidebar or top navigation:
// src/config/navigation.ts (or similar)
export const navigationItems = [
{ label: 'Books', path: '/books', icon: BookIcon },
{ label: 'My Feature', path: '/my-feature', icon: StarIcon },
];
Customizing shadcn/ui Components
Since shadcn/ui components live in src/components/ui/, you can modify them directly. For example, to change the default button variant or add a new variant, edit src/components/ui/button.tsx.