A production-grade React starter for BFSI (Banking, Financial Services & Insurance) applications. This repository is the canonical boilerplate for all BFSI front-end projects in our organisation — clone it, rename it, and start delivering features against the conventions that are already in place.
It bakes in:
- A secure-by-default stack (React 19 + Vite 5 + TypeScript strict).
- BFSI-specific guardrails: PII masking, in-memory tokens, Zod-validated boundaries, protected routes, encryption helpers.
- A complete Claude Code toolkit — skills, agents, and slash commands wired into
.claude/— so AI-assisted work follows the same conventions as a human reviewer. - Husky + lint-staged + commitlint with Conventional Commits (BFSI types).
- Auto-review on every Claude turn that flags P0/P1 issues before they ship.
- Tech stack
- Prerequisites
- Clone & run
- Available scripts
- Project structure
- Critical BFSI conventions
- Feature-folder pattern
- Layouts (
src/layouts/) - Auth, tokens, and 401 handling
- Client state with Zustand
- Environment variables
- Testing
- Performance baseline
- Claude Code toolkit
- Hooks & guardrails
- Commit & PR conventions
- Using this as a boilerplate
- Gotchas
| Layer | Choice |
|---|---|
| Framework | React 19 + Vite 5 + TypeScript (strict) |
| Styling | Tailwind CSS + shadcn/ui (components owned in src/components/ui/) |
| Forms | React Hook Form + Zod (@hookform/resolvers/zod) |
| Routing | react-router-dom v6 with <ProtectedRoute permission="..."> |
| Server state | TanStack Query v5 (@tanstack/react-query) |
| Client state | Zustand v5 (UI-only, never server data) |
| HTTP | axios with a single shared instance and in-memory token |
| i18n | react-i18next (en, hi defaults) |
| Tests (unit) | Vitest + Testing Library + jsdom |
| Tests (E2E) | Playwright |
| Lint / format | ESLint 9 (--max-warnings 0) + Prettier |
| Git hooks | Husky + lint-staged + commitlint |
- Node.js >= 20.x (LTS)
- npm >= 10.x (bundled with Node 20+)
- Git >= 2.40
- A modern browser (Chromium-based recommended for Playwright)
Windows: PowerShell 7 is supported. macOS / Linux: any POSIX shell.
# 1. Clone
git clone https://github.com/joshsoftware/react-ai-skills.git my-bank-app
cd my-bank-app
# 2. Remove the upstream git history and start fresh
rm -rf .git && git init
# 3. Install dependencies
npm install
# 4. Set up env — the app will throw a Zod error at boot if this is missing
cp .env.local.sample .env.local
# then open .env.local and fill in real values
# 5. Wire up Husky git hooks
git config core.hooksPath .husky
# 6. Start the dev server (http://localhost:5173)
npm run devYou should see the login reference feature at /login. Use it as the shape for every new feature you add.
npm run dev # Vite dev server on :5173 (with security headers enforced)
npm run build # tsc --noEmit && vite build (production bundle)
npm run preview # Preview the production build locally
npm test # Vitest — single run (CI mode)
npm run test:watch # Vitest — watch mode
npm run test:e2e # Playwright E2E tests
npm run typecheck # tsc --noEmit (no emit, just type-check)
npm run lint # ESLint (--max-warnings 0; warnings fail CI)
npm run lint:fix # ESLint with autofix
npm run format # Prettier write across src/**
npm run analyze # Bundle treemap -> dist/stats.html (auto-opens).
├── .claude/ Claude Code config: agents, skills, commands, hooks
│ ├── agents/ Sub-agents (security, code, a11y, PII, perf reviewers)
│ ├── skills/ Reference + action skills (bfsi-feature, bfsi-form, ...)
│ ├── commands/ Slash commands (/bfsi-review, /bfsi-scaffold, ...)
│ └── settings.json Permission rules + hook registration
├── .husky/ Git hooks (pre-commit, commit-msg)
├── src/
│ ├── api/ axiosInstance, http helpers (GET/POST/...), queryClient
│ ├── app/ App.tsx (providers), globals.css
│ ├── assets/ logo, images, fonts, icons (Vite-hashed imports — see assets/README.md)
│ ├── components/
│ │ ├── bfsi/ PIIMaskedDisplay and other BFSI primitives
│ │ ├── common/ FormInput, Image (typed wrappers)
│ │ └── ui/ shadcn-managed; add via `npx shadcn-ui@latest add <c>`
│ ├── constants/ endPoints.ts, statusCodes.ts, routes.ts (add queryKeys/regex/app as needed)
│ ├── features/
│ │ └── login/ Reference feature — copy its shape for new features
│ │ ├── components/ LoginForm.tsx
│ │ ├── hooks/ useLogin.ts (thin useMutation wrapper)
│ │ ├── __tests__/ Vitest specs (services, hooks, components, schema)
│ │ ├── index.tsx
│ │ ├── services.ts Typed service functions
│ │ ├── types.ts Request/response interfaces
│ │ └── utils.ts Zod schema + form defaults
│ ├── i18n/ react-i18next setup + en/hi translations
│ ├── layouts/ PublicLayout, AppLayout (shared chrome via <Outlet />)
│ ├── lib/
│ │ ├── encryption/ AES-GCM, RSA-OAEP, PBKDF2, envelope helpers
│ │ ├── http/ createAxios, interceptors, error helpers
│ │ ├── pii/ patterns, validators, maskers
│ │ └── utils/ cn() and small utilities
│ ├── routes/ ProtectedRoute + route config (layouts nest routes here)
│ ├── shared/ Cross-feature components (ErrorBoundary, Dashboard placeholder)
│ ├── env.ts Zod-validated env (throws at boot on bad config)
│ └── main.tsx Entry point
├── CLAUDE.md Project context loaded by Claude Code at session start
├── ARCHITECTURE.txt Detailed architecture guide
├── README.md This file
├── .env.local.sample Template — copy to .env.local
├── commitlint.config.cjs Conventional Commits with BFSI types
├── eslint.config.js Flat config; --max-warnings 0
├── tailwind.config.ts
├── tsconfig.json strict TypeScript
└── vite.config.ts
These are non-negotiable and enforced by hooks, lint rules, and review agents.
- Tokens never in
localStorage. UsesetAuthToken(axiosInstance, token)from@/lib/httpat login — the token lives in memory only. - API request/response shapes are typed with TypeScript interfaces from the feature's
types.ts, passed explicitly to the HTTP helpers (POST<IRequest, IResponse>). These are compile-time types. - PII fields display via
<PIIMaskedDisplay>. Never render PAN / Aadhaar / account number / customer ID directly. - No card data in HTML inputs. Use a tokenised card input component; flag any plain
<input>capturing PAN/CVV. - Every route is a
<ProtectedRoute permission="...">with an explicit permission string. Public routes are the exception, not the default. - No
dangerouslySetInnerHTMLunless explicitly sanitised. A pre-write hook blocks it. - No
console.logof PII variables (PAN, Aadhaar, account, password, OTP). A post-write hook scans for this. - Conventional Commits with BFSI types —
feat,fix,security,compliance,perf,refactor,docs,style,test,build,ci,chore. NoCo-Authored-Bytrailer. - Grep before you reference. Don't import a function/hook/component you haven't confirmed exists. The
bfsi-no-fabricationskill (and a PreToolUse hook) enforces this. - Constants are exhaustive. Adding an endpoint/route/tag means updating the centralised constants file — no inline strings.
The full working-discipline list is in CLAUDE.md.
Every feature owns its services, hooks, types, schemas, components, and tests. src/features/login/ is the working reference — copy its shape.
services.ts — typed async functions calling the HTTP helpers:
import { POST } from '@/api/http';
import { ENDPOINTS } from '@/constants/endPoints';
import type { ILoginRequest, ILoginResponse } from './types';
export const loginService = (payload: ILoginRequest): Promise<ILoginResponse> =>
POST<ILoginRequest, ILoginResponse>(ENDPOINTS.LOGIN, payload);Generic order is <TRequest, TResponse> — request first, response second.
hooks/useLogin.ts — thin useMutation wrapper, no baked-in side effects:
import { useMutation } from '@tanstack/react-query';
import { loginService } from '../services';
export const useLogin = () => useMutation({ mutationFn: loginService });Don't bake onSuccess / onError into the hook — pass them at the call-site.
utils.ts — Zod schema + inferred type + defaults:
import { z } from 'zod';
export const loginSchema = z.object({
username: z.string().min(3, 'Username must be at least 3 characters'),
password: z.string().trim().min(1, 'Password is required'),
});
export type ILoginFormValues = z.infer<typeof loginSchema>;
export const LOGIN_FORM_DEFAULT_VALUES: ILoginFormValues = { username: '', password: '' };Form value types are inferred from the schema — never hand-written.
To scaffold a new feature against this pattern, run /bfsi-feature inside Claude Code.
Two layouts ship by default. Both use the React-Router-v6 parent/<Outlet /> pattern.
| Layout | Used by | Chrome |
|---|---|---|
PublicLayout |
Login, public pages | Slim top bar with brand mark. No nav, no logout. |
AppLayout |
Dashboard and authenticated pages | Sticky header with brand + sign-out button (clears token + query cache, navigates to /login). |
Per-route permission stays at the leaf (<ProtectedRoute permission="...">), not the layout. Layouts are chrome only.
The shared axios instance has no per-request token interceptor. Set the token once on login success:
import { setAuthToken } from '@/lib/http';
import axiosInstance from '@/api/axiosInstance';
mutate(values, {
onSuccess: (response) => {
setAuthToken(axiosInstance, response.token);
navigate('/dashboard', { replace: true });
},
});On a 401, the instance's onUnauthorized callback clears the token and redirects to /login. See src/lib/http/interceptors.ts and the axios-auth skill for the full walkthrough.
TanStack Query owns server state. Zustand owns client (UI) state. They are complementary — never use Zustand to mirror data that came from the server.
- Server-owned data? -> TanStack Query (
useQuery/useMutation). Stop. - Local to one component / tight parent-child tree? ->
useState/useReducer. Stop. - Read in >= 3 distant components or shared across routes? -> Zustand.
- Config that almost never changes (theme, locale)? -> React Context.
Typical BFSI fits: idle-timer state, MFA challenge id + attempts, multi-step transaction wizard draft, toast/notification queue.
For full setup details, conventions, and the testing pattern, run /bfsi-zustand-store inside Claude Code or open .claude/skills/bfsi-zustand-store/SKILL.md.
src/env.ts validates every VITE_* variable through Zod at boot. If anything is missing or wrong, the app throws immediately — fix .env.local before continuing.
Required keys (see .env.local.sample for the full list):
| Variable | Purpose |
|---|---|
VITE_API_BASE_URL |
API origin |
VITE_API_TIMEOUT_MS |
axios timeout (ms) |
VITE_AUTH_HEADER_NAME |
Header used to attach the bearer token |
VITE_SENTRY_DSN |
Optional — leave empty to disable |
VITE_IDLE_TIMEOUT_MS |
Idle logout (ms) |
VITE_SENSITIVE_IDLE_TIMEOUT_MS |
Idle logout on transaction routes (ms) |
VITE_FEATURE_FLAGS_PROVIDER |
local or your provider |
Do not rename
.env.local.sampleto.env.example— a PreToolUse hook (env-file-convention.sh) blocks it.
- Unit / component:
npm test(Vitest + Testing Library + jsdom). Helpers live intests/setup.ts. - E2E:
npm run test:e2e(Playwright). - Patterns: see the
testing-patternsandbfsi-test-patternskills — they cover schema tests, service mocks, TanStack Query hook tests, RHF form tests, and BFSI-specific cases (PII masking, auth bypass, race conditions, a11y).
Reference tests are in src/features/login/__tests__/.
The boilerplate ships sensible defaults so every cloned project starts from the same baseline.
| Optimisation | Where it lives | Status |
|---|---|---|
| Vendor code splitting | vite.config.ts manualChunks |
Splits react, forms, i18n into separate chunks |
| Route lazy loading | src/routes/index.tsx — React.lazy() + <Suspense> |
Every feature page is its own chunk |
| Query caching | src/api/queryClient.ts |
staleTime 60s, gcTime 5m, retry once on 5xx |
| Image CLS prevention | src/components/common/Image.tsx |
Mandatory width/height; lazy + async by default |
| Bundle analyzer | npm run analyze |
rollup-plugin-visualizer gated on analyze event |
Per-feature tuning is documented in the bfsi-perf-react, bfsi-perf-real-time, bfsi-perf-virtualize-list, and perf-tuning skills.
This project is set up as a first-class Claude Code workspace. The .claude/ directory ships with skills, agents, slash commands, and hooks that encode every convention listed above.
New to the codebase? Run
/bfsi-onboardingfor a guided tour, or/bfsi-doctorto verify your local setup is healthy.
| Command | Purpose |
|---|---|
/bfsi-onboarding |
Guided tour for a new developer on the codebase |
/bfsi-doctor |
Health check — env, deps, .claude config, hook registration |
/bfsi-feature |
Scaffold a new feature folder (services + hooks + schema + form + tests) |
/bfsi-form |
Generate an RHF + Zod form against the project's FormInput wrapper |
/bfsi-pii-field |
Render a PII field via <PIIMaskedDisplay> with the right pattern |
/bfsi-api-endpoint |
Add a new endpoint to endPoints.ts and wire a typed service |
/bfsi-compliance-check |
Map current branch to RBI / PCI / IRDAI / SOC2 / ISO 27001 controls |
/bfsi-commit |
Stage and commit using Conventional Commits with the right BFSI type |
/bfsi-review |
Run the full PR review pipeline (security + code + a11y + PII + perf) |
/bfsi-grep-conventions |
Surface all // CONVENTION: breadcrumbs ready to codify |
/bfsi-scaffold |
Lower-level scaffolding for files the BFSI skills depend on |
Architecture & feature shape: tanstack-services, query-client-setup, constants-organization, bfsi-zustand-store, axios-auth
Forms, UI & accessibility: bfsi-design-system, bfsi-form, bfsi-pii-field, bfsi-data-table, bfsi-confirm-modal, bfsi-i18n-key
Security & compliance: bfsi-encrypt-helper, bfsi-error-message, bfsi-no-fabrication, bfsi-verify-backend, bfsi-regulation-quote, bfsi-protected-route, bfsi-route-constant
Performance: perf-tuning, bfsi-perf-react, bfsi-perf-real-time, bfsi-perf-virtualize-list
Testing: testing-patterns, bfsi-test-pattern
| Agent | Use |
|---|---|
bfsi-pr-reviewer |
Orchestrator — runs the full review pipeline in parallel |
bfsi-architect |
Design / sanity-check a feature before scaffolding |
bfsi-security-reviewer |
OWASP Top 10 + BFSI specifics (CSRF, secrets, weak crypto) |
bfsi-code-reviewer |
Readability, complexity, test coverage, type safety |
bfsi-accessibility-auditor |
WCAG 2.1 AA — semantics, ARIA, focus, keyboard, contrast |
bfsi-pii-scanner |
Find PII in console logs, URLs, errors, localStorage, telemetry |
bfsi-performance-reviewer |
Virtualisation, real-time feeds, render hotspots, bundle deltas |
bfsi-compliance-auditor |
RBI / PCI-DSS / IRDAI / SOC2 / ISO 27001 control-by-control report |
The repo ships with PreToolUse, PostToolUse, and Stop hooks registered in .claude/settings.json. Highlights:
no-fabrication-guard.sh— blocks fabricated imports where the symbol doesn't exist.protect-skill-mandated.sh— blocksrm/git rm/mvof skill-mandated files. Edits are fine; deletions require an explicit override.env-file-convention.sh— blocks renaming.env.local.sample.git-author-guard.sh— verifies commit author identity after every commit.- Auto-review on Stop — after every coding turn, a review sub-agent scans the uncommitted diff. If it finds P0 (security / PII / secrets) or P1 (convention violations), it shows the list and offers to fix them.
- Conventional Commits are enforced by
commitlint+husky/commit-msg. - Allowed types:
feat,fix,security,compliance,perf,refactor,docs,style,test,build,ci,chore. - No
Co-Authored-Bytrailer — keep commits owned by the actual author. npx lint-stagedruns onpre-commit.- Use
/bfsi-committo stage + commit with the right type chosen automatically.
When starting a new BFSI project from this repo:
- Clone the repo:
git clone https://github.com/joshsoftware/react-ai-skills.git my-new-app cd my-new-app rm -rf .git && git init
- Install dependencies:
npm install
- Rename the project in
package.json(name) and updateCLAUDE.mdwith project-specific context. - Set up environment:
cp .env.local.sample .env.local # edit .env.local with your real API URL and config - Wire Husky and make the initial commit:
git config core.hooksPath .husky git add -A git commit -m "chore: initialize project from BFSI boilerplate" - Set the upstream remote:
git remote add origin https://github.com/<your-org>/<new-repo>.git git push -u origin main
- Run
/bfsi-doctorin a Claude Code session to confirm everything is wired. - Build your first feature:
Set the canonical pattern in the first feature, commit it, then build the rest in fresh sessions primed off that commit.
/bfsi-feature KycVerification
Treat the conventions in
CLAUDE.mdand.claude/as the contract. Updates to the boilerplate should be PR'd back here so every BFSI project benefits.
.env.localis gitignored. If the app fails at boot with a Zod error, copy.env.local.sample->.env.localand fill in real values.src/components/ui/is shadcn-managed. Add components vianpx shadcn-ui@latest add <component>— don't hand-author there.- Dev server enforces tight security headers (
X-Frame-Options: DENY, etc.). If iframe embedding fails in dev, that's why. - ESLint runs with
--max-warnings 0. Warnings fail CI — fix them as they appear. - Husky is wired with
git config core.hooksPath .husky. If hooks stop firing, check that config first. - Fabricated imports are blocked by
no-fabrication-guard.sh. If you see it fail, the symbol doesn't exist — grep to find the real name. - Skill-mandated files can't be deleted by
rm/git rm. Edits are fine; deletions require an explicit override.
Internal — for use within the organisation. See your team lead for distribution policy.