CLAUDE.md
Using CLAUDE.md and AGENTS.md files for AI-powered development with React .NET Templates
AI-Assisted Development
CLAUDE.md Files in Every Template
Each React .NET Template includes a comprehensive CLAUDE.md file designed to provide AI assistants like Claude Code, Cursor, and other AI coding tools with the complete context needed to effectively work with your project.
These files act as a living guide that helps AI assistants understand:
- Project architecture and structure
- Common development commands and workflows
- Database migrations and ORM patterns
- API development conventions
- Testing strategies
- Deployment configurations
Template-Specific CLAUDE.md Files
Each template has its own CLAUDE.md and AGENTS.md files tailored to its specific architecture:
- react-static/AGENTS.md - Minimal Vite + React SPA template
- next-static/AGENTS.md - Minimal Next.js Static Site Generation template
- next-rsc/AGENTS.md - Minimal Next.js React Server Components template
- react-spa/AGENTS.md - Full featured Vite + React SPA template
- nextjs/AGENTS.md - Full featured Next.js Static Site Generation template
These files are automatically included when you create a new project, ensuring AI assistants have immediate access to your project's context.
Why CLAUDE.md?
When working with AI coding assistants, context is everything. The better the AI understands your project structure, conventions, and patterns, the more accurate and helpful its suggestions become. CLAUDE.md files provide this context upfront, enabling:
- Faster onboarding - AI assistants immediately understand your project structure
- Better code generation - AI generates code that follows your conventions
- Accurate suggestions - AI recommends the right tools and patterns for your stack
- Consistent patterns - AI maintains architectural consistency across features
What's Inside CLAUDE.md
Below is the complete CLAUDE.md from the react-static template, which serves as a comprehensive reference for AI-assisted development:
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) and other Agents when working with code in this repository.
Project Overview
A full-stack .NET 10 + React 19 + Next.js template combining ServiceStack backend with Next.js static site generation. Uses ASP.NET Core Identity for auth, OrmLite for application data, and Entity Framework Core for Identity data management.
Common Commands
Development
# Start both .NET and Vite dev servers (from project root)
dotnet watch
# After making changes to C# DTOs restart .NET before regenerating TypeScript DTOs by running:
cd MyApp.Client && npm run dtosBuilding
# Build frontend (TypeScript + Vite)
cd MyApp.Client && npm run build
# Build backend (.NET)
dotnet build
# Build Tailwind CSS for Razor Pages
cd MyApp && npm run ui:buildTesting
# Frontend tests (Vitest)
cd MyApp.Client && npm run test # Watch mode
cd MyApp.Client && npm run test:ui # UI mode
cd MyApp.Client && npm run test:run # Single run
# Backend tests (NUnit)
dotnet testDatabase Migrations
# Run all migrations (both EF Core and OrmLite)
cd MyApp && npm run migrate
# Entity Framework migrations (for changes to Identity tables)
dotnet ef migrations add MigrationName
dotnet ef database update
# Revert last migration
cd MyApp && npm run revert:last
# Drop and re-run last migration (useful during development)
cd MyApp && npm run rerun:lastAutoQuery CRUD Development (using okai)
# Create new AutoQuery CRUD feature with TypeScript data model
npx okai init Table
# Regenerate C# AutoQuery APIs and DB migration from .d.ts model
npx okai Table.d.ts
# Remove AutoQuery feature and all generated code
npx okai rm Table.d.tsArchitecture
Hybrid Development/Production Model
Development Mode:
dotnet watchfrom MyApp starts .NET (port 5001) and Next.js dev server (port 3000), accesible viahttps://localhost:5001- ASP.NET Core proxies requests to Next.js dev server via
NodeProxy(configured in Program.cs) - Hot Module Replacement (HMR) enabled via WebSocket proxying using
MapNotFoundToNode,MapNextHmr,RunNodeProcess,MapFallbackToNodein Program.cs
Production Mode:
- Next.js builds React app to
MyApp.Client/dist/, which is copied toMyApp/wwwroot/when published - ASP.NET Core serves static files directly from
wwwroot- no Node.js required - Fallback to
index.htmlfor client-side routing
Modular Startup Configuration
AppHost uses .NET's IHostingStartup pattern to split configuration across multiple files in MyApp/:
- Configure.AppHost.cs - Main ServiceStack AppHost registration
- Configure.Auth.cs - ServiceStack AuthFeature with ASP.NET Core Identity integration
- Configure.AutoQuery.cs - AutoQuery features and audit events
- Configure.Db.cs - Database setup (OrmLite for app data, EF Core for Identity)
- Configure.Db.Migrations.cs - Runs OrmLite and EF DB Migrations and creates initial users
- Configure.BackgroundJobs.cs - Background job processing
- Configure.HealthChecks.cs - Health monitoring endpoint
This pattern keeps Program.cs clean and separates concerns. Each Configure.*.cs file is auto-registered via [assembly: HostingStartup] attribute.
Project Structure
MyApp/ # .NET Backend (hosts both .NET and Next.js)
├── Configure.*.cs # Modular startup configuration
├── Migrations/ # EF Core Identity migrations + OrmLite app migrations
├── Pages/ # Identity Auth Razor Pages
└── wwwroot/ # Production static files (from MyApp.Client/dist)
MyApp.Client/ # React Frontend
├── app/ # Next.js App Router pages
├── components/ # React components
├── lib/ # Utilities and helpers
│ ├── dtos.ts # Auto-generated from C# (via `npm run dtos`)
│ ├── gateway.ts # ServiceStack JsonServiceClient
│ └── utils.ts # Utility functions
├── public/ # Static assets
├── dist/ # Build output (production)
├── styles/ # Tailwind CSS styles
└── next.config.mjs # Next.js config for dev mode
MyApp.ServiceModel/ # DTOs & API contracts
├── *.cs # C# Request/Response DTOs
├── api.d.ts # TypeScript data models Schema
└── *.d.ts # TypeScript data models for okai code generation
MyApp.ServiceInterface/ # Service implementations
├── Data/ # EF Core DbContext and Identity models
└── *Services.cs # ServiceStack service implementations
MyApp.Tests/ # .NET tests (NUnit)
├── IntegrationTest.cs # API integration tests
└── MigrationTasks.cs # Migration task runner
config/
└── deploy.yml # Kamal deployment settings
.github/
└── workflows/
├── build.yml # CI build and test
├── build-container.yml # Container image build
└── release.yml # Production deployment with KamalDatabase Architecture
Dual ORM Strategy:
- OrmLite: All application data (faster, simpler, typed POCO ORM)
- Entity Framework Core: ASP.NET Core Identity tables only (Users, Roles, etc.)
Both use the same SQLite database by default (App_Data/app.db). Connection string in appsettings.json.
Migration Files:
MyApp/Migrations/20240301000000_CreateIdentitySchema.cs- EF Core migration for IdentityMyApp/Migrations/Migration1000.cs- OrmLite migration for app tables (e.g., Booking)
Run npm run migrate to execute both.
Authentication Flow
- ASP.NET Core Identity handles user registration/login via Razor Pages at
/Identity/*routes - ServiceStack AuthFeature integrates with Identity via
IdentityAuth.For<ApplicationUser>()in Configure.Auth.cs - Custom claims added via
AdditionalUserClaimsPrincipalFactoryandCustomUserSession - ServiceStack services use
[ValidateIsAuthenticated]and[ValidateHasRole]attributes for authorization (see Bookings.cs)
ServiceStack .NET APIs
ServiceStack APIs adopt a DTOs-first approach utilizing message-based APIs. To create ServiceStack APIs create all related DTOs used in the API (aka Service Contracts) into a single file in the MyApp.ServiceModel project, e.g:
//MyApp.ServiceModel/Bookings.cs
public class GetBooking : IGet, IReturn<GetBookingResponse>
{
[ValidateGreaterThan(0)]
public int Id { get; set; }
}
public class GetBookingResponse
{
public Booking? Result { get; set; }
public ResponseStatus? ResponseStatus { get; set; }
}The response type of an API should be specified in the IReturn<Response> marker interface. APIs which don't return a response should implement IReturnVoid instead.
By convention, APIs return single results in a T? Result property, APIs returns multiple results of the same type in a List<T> Results property. Otherwise APIs returning results of different types should use intuitive property names in a flat structured Response DTO for simplicity.
These C# Server DTOs are used to generate the TypeScript dtos.ts.
Validating APIs
Any API Errors are automatically populated in the ResponseStatus property, inc. Declarative Validation Attributes like [ValidateGreaterThan] and [ValidateNotEmpty] which validate APIs and return any error responses in ResponseStatus.
Protecting APIs
The Type Validation Attributes below should be used to protect APIs:
[ValidateIsAuthenticated]- Only Authenticated Users[ValidateIsAdmin]- Only Admin Users[ValidateHasRole]- Only Authenticated Users assigned with the specified role[ValidateApiKey]- Only Users with a valid API Key
//MyApp.ServiceModel/Bookings.cs
[ValidateHasRole("Employee")]
public class CreateBooking : ICreateDb<Booking>, IReturn<IdResponse>
{
//...
}Primary HTTP Method
APIs have a primary HTTP Method which if not specified uses HTTP POST. Use IGet, IPost, IPut, IPatch or IDelete to change the HTTP Verb except for AutoQuery APIs which have implied verbs for each CRUD operation.
API Implementations
ServiceStack API implementations should be added to MyApp.ServiceInterface/:
//MyApp.ServiceInterface/BookingServices.cs
public class BookingServices(IAutoQueryDb autoquery) : Service
{
public object Any(GetBooking request)
{
return new GetBookingResponse {
Result = base.Db.SingleById<Booking>(request.Id)
?? throw HttpError.NotFound("Booking does not exist")
};
}
// Example of overriding an AutoQuery API with a custom implementation
public async Task<object> Any(QueryBookings request)
{
using var db = autoQuery.GetDb(request, base.Request);
var q = autoQuery.CreateQuery(request, base.Request, db);
return await autoQuery.ExecuteAsync(request, q, base.Request, db);
}
}APIs can be implemented with sync or async methods using Any or its primary HTTP Method e.g. Get, Post.
The return type of an API implementation does not change behavior however returning object is recommended so its clear the Request DTO IReturn<Response> interface defines the APIs Response type and Service Contract.
The ServiceStack Service base class has convenience properties like Db to resolve an Open IDbConnection for that API and base.Request to resolve the IRequest context. All other dependencies required by the API should use constructor injection in a Primary Constructor.
A ServiceStack API typically returns the Response DTO defined in its Request DTO IReturn<Response> or an Error but can also return any raw custom Return Type like string, byte[], Stream, IStreamWriter, HttpResult and HttpError.
AutoQuery CRUD Pattern
ServiceStack's AutoQuery generates full CRUD APIs from declarative request DTOs. Example in Bookings.cs:
QueryBookings : QueryDb<Booking>→ GET /api/QueryBookings with filtering/sorting/pagingCreateBooking : ICreateDb<Booking>→ POST /api/CreateBookingUpdateBooking : IPatchDb<Booking>→ PATCH /api/UpdateBookingDeleteBooking : IDeleteDb<Booking>→ DELETE /api/DeleteBooking
No service implementation required - AutoQuery handles it. Audit fields (CreatedBy, ModifiedBy, etc.) auto-populated via [AutoApply(Behavior.AuditCreate)] attributes.
TypeScript DTO Generation
After changing C# DTOs in MyApp.ServiceModel/, restart the .NET Server then run:
cd MyApp.Client && npm run dtosThis calls ServiceStack's /types/typescript endpoint and updates dtos.ts with type-safe client DTOs. The Vite dev server auto-reloads.
okai AutoQuery Code Generation
The npx okai tool generates C# AutoQuery APIs and migrations from TypeScript data models (.d.ts files):
- TypeScript data model (
MyApp.ServiceModel/Bookings.d.ts) defines the entity with decorators - C# AutoQuery APIs (
MyApp.ServiceModel/Bookings.cs) - auto-generated CRUD request/response DTOs - C# OrmLite migration (
MyApp/Migrations/Migration1000.cs) - auto-generated schema creation
This enables rapid prototyping: edit the .d.ts model, run npx okai Bookings.d.ts, then npm run migrate.
Important: The .d.ts files use special decorators (e.g., @validateHasRole, @autoIncrement) that map to C# attributes and .NET Types. The valid schema for these is defined in api.d.ts. Reference Bookings.d.ts for examples.
AutoQuery APIs
C# AutoQuery APIs allow creating queryable C# APIs for RDBMS Tables with just a Request DTO definition, e.g:
public class QueryBookings : QueryDb<Booking>
{
public int? Id { get; set; }
public decimal? MinCost { get; set; }
public List<decimal>? CostBetween { get; set; }
public List<int>? Ids { get; set; }
}It uses these conventions to determine the behavior of each property filter:
ImplicitConventions = new() {
{"%Above%", "{Field} > {Value}"},
{"Begin%", "{Field} > {Value}"},
{"%Beyond%", "{Field} > {Value}"},
{"%Over%", "{Field} > {Value}"},
{"%OlderThan", "{Field} > {Value}"},
{"%After%", "{Field} > {Value}"},
{"OnOrAfter%", "{Field} >= {Value}"},
{"%From%", "{Field} >= {Value}"},
{"Since%", "{Field} >= {Value}"},
{"Start%", "{Field} >= {Value}"},
{"%Higher%", "{Field} >= {Value}"},
{"Min%", "{Field} >= {Value}"},
{"Minimum%", "{Field} >= {Value}"},
{"Behind%", "{Field} < {Value}"},
{"%Below%", "{Field} < {Value}"},
{"%Under%", "{Field} < {Value}"},
{"%Lower%", "{Field} < {Value}"},
{"%Before%", "{Field} < {Value}"},
{"%YoungerThan", "{Field} < {Value}"},
{"OnOrBefore%", "{Field} < {Value}"},
{"End%", "{Field} < {Value}"},
{"Stop%", "{Field} < {Value}"},
{"To%", "{Field} < {Value}"},
{"Until%", "{Field} < {Value}"},
{"Max%", "{Field} < {Value}"},
{"Maximum%", "{Field} < {Value}"},
{"%GreaterThanOrEqualTo%", "{Field} >= {Value}"},
{"%GreaterThan%", "{Field} > {Value}"},
{"%LessThan%", "{Field} < {Value}"},
{"%LessThanOrEqualTo%", "{Field} < {Value}"},
{"%NotEqualTo", "{Field} <> {Value}"},
{"Like%", "UPPER({Field}) LIKE UPPER({Value})"},
{"%In", "{Field} IN ({Values})"},
{"%Ids", "{Field} IN ({Values})"},
{"%Between%", "{Field} BETWEEN {Value1} AND {Value2}"},
{"%HasAll", "{Value} & {Field} = {Value}"},
{"%HasAny", "{Value} & {Field} > 0"},
{"%IsNull", "{Field} IS NULL"},
{"%IsNotNull", "{Field} IS NOT NULL"},
};Each convention key includes % wildcards to define where a DataModel field names can appear, either as a Prefix, Suffix or both. The convention value describes the SQL filter that gets applied to the query when the property is populated.
Properties that matches a DataModel field performs an exact query {Field} = {Value}, e.g:
const api = client.api(new QueryBookings({ id:1 }))As MinCost matches the "Min%" convention it applies the Cost >= 100 filter to the query:
const api = client.api(new QueryBookings({ minCost:100 }))As CostBetween matches the "%Between%" convention it applies the Cost BETWEEN 100 AND 200 filter to the query:
const api = client.api(new QueryBookings({ costBetween:[100,200] }))AutoQuery also matches on pluralized fields where Ids matches Id and applies the Id IN (1,2,3) filter:
const api = client.api(new QueryBookings({ ids:[1,2,3] }))Multiple Request DTO properties applies mutliple AND filters, e.g:
const api = client.api(new QueryBookings({ minCost:100, ids:[1,2,3] }))Applies the (Cost >= 100) AND (Id IN (1,2,3)) filter.
Key Conventions
API Client Usage
Frontend code imports from lib/gateway.ts:
import { client } from '@/lib/gateway'
import { QueryBookings } from '@/lib/dtos'
const response = await client.api(new QueryBookings())The client is a configured JsonServiceClient pointing to /api (proxied to .NET backend).
All .NET APIs are accessible by Request DTOs which implement either a IReturn<ResponseType> a IReturnVoid interface which defines the API Response, e.g:
export class Hello implements IReturn<HelloResponse>, IGet
{
public name: string;
public constructor(init?: Partial<Hello>) { (Object as any).assign(this, init); }
}
export class HelloResponse
{
public result: string;
public constructor(init?: Partial<HelloResponse>) { (Object as any).assign(this, init); }
}ServiceStack API Client Pattern
Inside a React Component use useClient() to resolve a Service Client. The ApiResult can hold loading, failed and successful API Response states, e.g:
type Props = { value: string }
export default ({ value }:Props) => {
const [name, setName] = useState(value)
const client = useClient()
const [api, setApi] = useState<ApiResult<HelloResponse>>(new ApiResult())
useEffect(() => {
(async () => {
setApi(new ApiResult())
setApi(await client.api(new Hello({ name })))
})()
}, [name])
return (<div>
<TextInput id="name" label="API Example" value={name} onChange={setName} />
{api.error
? <div className="text-red-500">{api.error.message}</div>
: api.succeeded
? <div className="text-gray-900">{api.response.result}</div>
: <div>loading...</div>}
</div>)
}All client api, apiVoid and apiForm methods never throws exceptions - it always returns an ApiResult<T> which contains either a response for successful responses or an error with a populated ResponseStatus, as such using try/catch around client.api* calls is always wrong as it implies it would throw an Exception, when it never does.
The examples below show typical usage:
The api and apiVoid APIs return an ApiResult<Response> which holds both successful and failed API Responses:
const api = await client.api(new Hello({ name }))
if (api.succeeded) {
console.log(`The API succeded:`, api.response)
} else if (api.error) {
console.log(`The API failed:`, api.error)
}The apiForm API can use a HTML Form's FormData for its Request Body together with an APIs empty Request DTO, e.g:
const submit = async (e: React.FormEvent) => {
const form = e.currentTarget as HTMLFormElement
const api = await client.apiForm(new CreateContact(), new FormData(form))
if (api.succeeded) {
console.log(`The API succeded:`, api.response)
} else if (api.error) {
console.log(`The API failed:`, api.error)
}
}Using apiForm is required for multipart/form-data File Uploads.
ServiceStack Form Components
The @servicestack/react component library have several components to simplify UI generation.
The <AutoForm> Component can be used to render an API validation bound form for any Request DTO.
import { AutoForm, AutoCreateForm, AutoEditForm, HtmlFormat } from '@servicestack/react'
function GenericFormExample() {
const [results, setResults] = useState<Booking[]|undefined>()
const onSuccess = (response:QueryResponse<Booking>) => {
setResults(response.results)
}
return (
<AutoForm panelClass="mx-auto max-w-3xl" type="QueryBookings" onSuccess={onSuccess} />
{results && <HtmlFormat value={results} />}
)
}The <AutoCreateForm> can be used with an AutoQuery CRUD ICreateDb<T> DTO to render a create entity form.
<AutoCreateForm type="CreateBooking" formStyle="card" />The <AutoEditForm> can be used with an AutoQuery CRUD IPatchDb<T> or IUpdateDb<T> DTO to render an update form.
The deleteType can be set to use an IDeleteDb<T> DTO to enable delete functionality.
function EditFormExample({ booking }:{ Booking:booking }) {
return (
<AutoEditForm
value={booking}
type="UpdateBooking"
deleteType="DeleteBooking"
heading="Change an existing Room Booking"
subHeading="Manage reservations for MyApp hotels."
formStyle="card"
/>)
}ApiStateContext and Input Components
The ApiStateContext can be used to inject an APIs Error ResponseStatus down to all @servicestack/react Input components.
import {ErrorSummary, TextInput, PrimaryButton, useClient, ApiStateContext} from "@servicestack/react"
function CustomFormExample() {
const client = useClient()
const [userName, setUserName] = useState<string|undefined>()
const [password, setPassword] = useState<string|undefined>()
const onSubmit = async (e:SyntheticEvent<HTMLFormElement>) => {
e.preventDefault()
const api = await client.api(new Authenticate({ provider: 'credentials', userName, password }))
if (api.succeeded) {
console.log('Signed In!', api.response)
} else if (api.error.errorCode === 'Unauthorized') {
console.log('Sign In failed:', api.error.message)
}
}
return (<ApiStateContext.Provider value={client}>
<form onSubmit={onSubmit}>
<ErrorSummary except="userName,password,rememberMe"/>
<div>
<TextInput id="userName" help="Email you signed up with" autoComplete="email"
value={userName} onChange={setUserName}/>
<TextInput id="password" type="password" help="6 characters or more"
value={password} onChange={setPassword}/>
<PrimaryButton>Log in</PrimaryButton>
</div>
</form>
</ApiStateContext.Provider>)
}All field errors are displayed next to their Input component all other API errors are displayed with the <ErrorSummary> component. Use except to avoid displaying field errors which are already displayed next to their associated input component.
If needed, the error ResponseStatus can be passed to components using its status property.
Routing
/api/*→ ServiceStack services/Identity/*→ ASP.NET Core Identity Razor Pages/ui/*→ ServiceStack API Explorer/admin-ui/*→ ServiceStack Admin UI (requires Admin role)/types/typescript→ ServiceStack .NET API TypeScript DTOs (for dtos.ts)- All other routes → React SPA (via fallback in dev/prod)
Razor Pages Integration
The template includes Razor Pages for Identity UI (/Identity routes) that coexist with the React SPA. These use Tailwind CSS compiled from MyApp/tailwind.input.css to MyApp/wwwroot/css/app.css.
Environment Variables
KAMAL_DEPLOY_HOST- Production hostname for deployment
Background Jobs
Configured in Configure.BackgroundJobs.cs using BackgroundsJobFeature. Jobs are commands that implement IAsyncCommand<T>.
Development Workflow
- Start dev servers:
dotnet watch(starts both .NET and Vite) - Make backend changes: Edit C# files in
MyApp.ServiceModelorMyApp.ServiceInterface - Restart .NET Server
- Regenerate DTOs:
cd MyApp.Client && npm run dtos - Make frontend changes: Edit React files in
MyApp.Client/src - Add new CRUD feature:
npx okai init Feature- Edit
MyApp.ServiceModel/Feature.d.ts npx okai Feature.d.tsnpm run migrate
Docs: AutoQuery Dev Worfklow
Admin Features
/admin-ui- ServiceStack Admin UI (database, users, API explorer)/admin-ui/users- User management (requires Admin role)/up- Health check endpoint
Deployment
GitHub Actions workflows in .github/workflows/ uses Kamal for Deployments:
build.yml- CI build and testbuild-container.yml- Docker image buildrelease.yml- Kamal deployment to production
Configure KAMAL_DEPLOY_HOST in GitHub secrets for your hostname. Kamal config in config/deploy.yml derives service names from repository name.
Using CLAUDE.md with AI Coding Tools
When working with AI assistants, simply reference this file or open it in your editor to provide the AI with full project context:
With Claude Code
# Claude Code automatically reads CLAUDE.md files when available
claude-codeWith Cursor
- Open CLAUDE.md in your editor
- Use
Cmd/Ctrl + Lto add it to the chat context - Ask questions or request features
With GitHub Copilot Chat
@workspace /explain Check the CLAUDE.md file for project conventions