brokle-frontend-dev
Use this skill when developing, implementing, or modifying Next.js/React frontend code for the Brokle web application. This includes creating components, pages, hooks, API clients, stores, or any frontend features. Invoke this skill at the start of frontend development tasks.
When & Why to Use This Skill
This Claude skill streamlines Next.js and React frontend development for the Brokle web application. It ensures high-quality code production by enforcing a strict feature-based architecture, modern tech stack standards (Next.js 15, React 19, Tailwind 4), and specific state management patterns. By automating the creation of components, hooks, and API clients according to predefined project rules, it minimizes architectural debt and accelerates the delivery of scalable frontend features.
Use Cases
- Scaffolding new domain-specific features within the modular 'features/' directory, ensuring proper public API exports via index files.
- Implementing complex server-state logic using React Query hooks and client-side global state with Zustand stores.
- Developing type-safe forms and UI components using shadcn/ui primitives, React Hook Form, and Zod validation.
- Refactoring legacy frontend code to align with the Brokle architecture, such as migrating internal path imports to feature-based aliases.
- Generating optimized Next.js Server and Client components while adhering to strict SEO and interactivity guidelines.
| name | brokle-frontend-dev |
|---|---|
| description | Use this skill when developing Next.js/React frontend code for Brokle. Includes components, pages, hooks, API clients, stores, or any frontend features. |
Brokle Frontend Development
Tech Stack
| Technology | Version | Purpose |
|---|---|---|
| Next.js | 15.5.2 | App Router, Turbopack |
| React | 19.2.0 | UI library |
| TypeScript | 5.9.3 | Strict mode |
| Tailwind CSS | 4.1.15 | Styling |
| shadcn/ui | - | Component primitives |
| Zustand | - | Client state |
| React Query | - | Server state |
| React Hook Form | - | Forms + Zod validation |
| Vitest | - | Testing |
| pnpm | - | Package manager |
Critical Import Rules
Always import from feature index, never internal paths:
// CORRECT
import { useAuth, SignInForm } from '@/features/authentication'
import { useOrganization } from '@/features/organizations'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import { apiClient } from '@/lib/api/core/client'
// FORBIDDEN - internal paths
import { useAuth } from '@/features/authentication/hooks/use-auth'
import { AuthStore } from '@/features/authentication/stores/auth-store'
Feature-Based Architecture
web/src/
├── app/ # Next.js App Router (routing only)
│ ├── (auth)/ # Auth route group
│ └── (dashboard)/ # Dashboard routes
├── features/ # Domain features (self-contained)
│ ├── authentication/
│ ├── organizations/
│ ├── projects/
│ └── [feature]/
│ ├── components/ # Feature UI
│ ├── hooks/ # React hooks
│ ├── api/ # API functions
│ ├── stores/ # Zustand (optional)
│ ├── types/ # TypeScript types
│ └── index.ts # Public API exports
├── components/
│ ├── ui/ # shadcn/ui primitives
│ ├── shared/ # Cross-feature components
│ └── layout/ # App shell
├── lib/api/core/ # BrokleAPIClient
└── hooks/ # Global hooks
Rule: Routes are thin - delegate to feature components:
// app/(dashboard)/projects/page.tsx
import { ProjectsList } from '@/features/projects'
export default function ProjectsPage() {
return <ProjectsList />
}
State Management
| State Type | Tool | Location |
|---|---|---|
| Server data | React Query | Feature hooks |
| Client state | Zustand | Feature stores |
| Form state | React Hook Form + Zod | Component |
| URL state | useSearchParams() | Component |
Server State Pattern
// features/[feature]/hooks/use-data.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { getData, updateData } from '../api/data-api'
export function useData(id: string) {
const queryClient = useQueryClient()
const query = useQuery({
queryKey: ['data', id],
queryFn: () => getData(id),
})
const mutation = useMutation({
mutationFn: updateData,
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['data', id] }),
})
return { ...query, update: mutation.mutate }
}
Client State Pattern
// features/[feature]/stores/feature-store.ts
import { create } from 'zustand'
interface FeatureState {
selectedId: string | null
setSelectedId: (id: string | null) => void
}
export const useFeatureStore = create<FeatureState>((set) => ({
selectedId: null,
setSelectedId: (id) => set({ selectedId: id }),
}))
Component Patterns
Server Components (Default)
No directive needed. Use for static content and SEO:
export default async function Page({ params }: { params: { id: string } }) {
const data = await fetchData(params.id)
return <div>{data.title}</div>
}
Client Components
Add 'use client' when needed:
'use client'
import { useState } from 'react'
export function Interactive() {
const [count, setCount] = useState(0)
return <Button onClick={() => setCount(c => c + 1)}>{count}</Button>
}
Use 'use client' when:
- Event handlers (onClick, onChange)
- React hooks (useState, useEffect)
- Browser APIs (localStorage, window)
API Client Pattern
// features/[feature]/api/feature-api.ts
import { apiClient } from '@/lib/api/core/client'
import type { FeatureResponse, CreateFeatureRequest } from '../types'
export async function getFeature(id: string): Promise<FeatureResponse> {
const response = await apiClient.get<FeatureResponse>(`/feature/${id}`)
return response.data
}
export async function createFeature(data: CreateFeatureRequest): Promise<FeatureResponse> {
const response = await apiClient.post<FeatureResponse>('/feature', data)
return response.data
}
Form Pattern
'use client'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
const schema = z.object({
email: z.string().email(),
name: z.string().min(2),
})
type FormData = z.infer<typeof schema>
export function MyForm() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
})
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Input {...register('email')} />
{errors.email && <p className="text-destructive">{errors.email.message}</p>}
</form>
)
}
Feature Index Pattern
// features/[feature]/index.ts
// Hooks
export { useFeature } from './hooks/use-feature'
// Components
export { FeatureComponent } from './components/feature-component'
// Types (selective)
export type { FeatureData, FeatureConfig } from './types'
// DO NOT export stores directly - use hooks
Testing
// features/[feature]/__tests__/component.test.tsx
import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import { Component } from '../components/component'
describe('Component', () => {
it('renders', () => {
render(<Component />)
expect(screen.getByText('Expected')).toBeInTheDocument()
})
})
Development Commands
cd web
pnpm dev # Dev server (Turbopack)
pnpm build # Production build
pnpm lint # Next.js linter
pnpm format # Prettier
pnpm test # Vitest
pnpm test:watch # Watch mode
Supporting References
Load these files when you need detailed guidance:
| Need | Reference |
|---|---|
| Colors, typography, spacing, dark mode | design-system.md |
| UI components, state patterns, forms | component-patterns.md |
| Animations, transitions, micro-interactions | animation-patterns.md |
| Complete feature example (authentication) | feature-patterns.md |
| Decision trees, cheat sheets | quick-reference.md |
Quick Decision Tree
Creating new functionality?
- Feature-specific →
features/[feature]/ - Shared across features →
components/shared/ - UI primitive →
components/ui/
Adding state?
- Server data → React Query hook
- Client state → Zustand store
- Form → React Hook Form
- URL → useSearchParams()
Component type?
- Needs interactivity/hooks →
'use client' - Static/SEO → Server Component (default)
Best Practices
- Import from feature index - Never from internal paths
- Keep routes thin - Delegate to feature components
- Server Components default - Add 'use client' only when needed
- Type everything - No
anytypes - Error boundaries - Add per feature and route
- Check existing components -
components/shared/before creating new