I structure React apps so the same shape repeats at every scale, and so small pieces get more valuable the more they are reused. Fractals and compounding are the two metaphors I use for that, and applied to a codebase they mean a consistent, layered design where extensions don't pile on debt.
Fractals: self-similarity
A fractal keeps the same shape at every scale, the way a fern leaflet looks like the whole frond. I want that in a codebase too, where a route folder looks like a feature folder and a component folder looks like both.
Compounding in finance and code
In finance, compounding means earning returns on reinvested returns, so small, regular contributions grow into large balances over time. In software, the equivalent is a system where each small improvement or extension adds value without adding debt. Fractal components are reusable building blocks that share one shape, so you can extend the system without inventing a new pattern each time.
React fractal components
In React, fractal design combines self-similarity with modularity through four practices:
-
Uniform folder structures: every folder, whether it holds a route, a feature, or a component, mirrors the same layout.
-
One stack everywhere: every component follows the same standards:
- tanstack/react-query for data fetching and caching with a predictable query model.
- viem for low-level Ethereum interactions with a modern TypeScript API.
- wagmi for React hooks over Ethereum, built on react-query and viem so chain reads follow the same data-fetching pattern.
- nuqs for URL-based state, which decouples components from each other's internal logic.
- Supabase as the backend, with API logic in dedicated folders so backend and frontend code stay portable.
-
A shared design system: shadcn/ui and Tailwind CSS keep the UI consistent and portable, including for generative UI workflows.
-
Collocation with AHA: components are extracted only when reuse becomes obvious, following Avoid Hasty Abstractions.
Folder structure
Each package repeats the same hierarchy, so a developer who knows one folder can predict the next:
repo/
- supabase/
- src/
- market/
- index.ts
- types.ts
- account/
- index.ts
- types.ts
- hooks/
- src/
- market/
- index.ts
- types.ts
- use-markets.ts
- account/
- index.ts
- types.ts
- use-account.ts
- app/
- src/
- market/
- list/
- index.tsx
- types.ts
- detail/
- use-market-collateral.ts
- index.tsx
- types.ts
- account/
- index.tsx
- hooks/
- use-account-health.ts
- use-account-positions.ts
- types.ts
- health.tsx
- positions.tsx
- types.ts Data fetching with React Query
Data fetching follows one pattern: the query function lives in the Supabase package and takes its client as an argument, so the same function runs from a hook on the frontend or from server code, and the hook only adds caching:
// repo/hooks/useMarkets.ts
import { useQuery } from '@tanstack/react-query'
import { createSupabaseClient } from '@repo/db'
import { getMarkets } from '@/api/market'
export function useMarkets() {
return useQuery({
queryKey: ['markets'],
queryFn: async () => {
const supabase = await createSupabaseClient()
return getMarkets({ supabase })
}
})
}