A personal React starter template built with modern tooling, file-based routing, and a component system based on Material Design 3.
| Layer | Technology |
|---|---|
| Framework | React 19 + TypeScript 6 |
| Build | Vite 8 |
| Routing | TanStack Router (file-based) |
| Server state | TanStack Query |
| HTTP client | superagent + superagent-prefix |
| Client state | Zustand + zustand-slices |
| UI / Design system | @m3e/react (Material Design 3) |
| Linting & formatting | Biome |
| Testing | Vitest + Testing Library |
| Package manager | Bun |
bun install
cp .env.example .env
bun dev| Variable | Description |
|---|---|
VITE_APP_NAME |
Application name |
VITE_API_URL |
Base URL for the API client |
| Command | Description |
|---|---|
bun dev |
Start the dev server |
bun build |
Type-check and build for production |
bun preview |
Preview the production build |
bun test |
Run tests |
bun test:ui |
Run tests with the Vitest UI |
bun test:coverage |
Run tests with coverage |
bun lint |
Lint src/ with Biome |
bun format |
Format src/ with Biome |
bun check |
Lint + format src/ with Biome |
src/
├── core/
│ ├── app.tsx # Entry point — router + React root
│ ├── clients/ # HTTP client (http-client.ts, api.stores.ts)
│ ├── layouts/ # Reusable layout components (BaseLayout)
│ ├── providers/ # AppProvider, QueryProvider, StoreProvider, ThemeProvider
│ ├── routes/ # File-based routes (__root, _rootLayout)
│ └── stores/ # Zustand store setup (app.store, devtools.store)
├── features/
│ └── theme/
│ ├── components/ # Theme component system (Card, Text, Icon, Button)
│ ├── theme.module.css
│ └── theme.stores.ts
└── shared/
└── base.types.ts
Components are exposed through a single Theme namespace:
import { Theme } from '@/features/theme/components'
<Theme.Text variant="display" size="large">Hello</Theme.Text>
<Theme.Button>Click me</Theme.Button>
<Theme.Icon>home</Theme.Icon>
<Card>
<Card.Header>Title</Card.Header>
<Card.Content>Body</Card.Content>
<Card.Footer>Footer</Card.Footer>
<Card.Actions>Actions</Card.Actions>
</Card>The color scheme (light, dark, auto) is managed via useThemeStore. The auto mode resolves to the user's OS preference via getPreferredColorScheme().
import { useThemeStore } from '@/features/theme/theme.stores'
const { theme, setTheme } = useThemeStore()
setTheme('dark') // 'light' | 'dark' | 'auto'The project includes a VSCode debug configuration for Chrome. Press F5 to start the dev server and attach the debugger.
Breakpoints in .tsx files work out of the box. The Start project task (bun dev) runs automatically before the debugger attaches.
HTTP requests are made with superagent. The createApiClient factory creates a typed Agent with optional domain prefix via superagent-prefix.
import { createApiClient } from '@/core/clients/http-client'
const client = createApiClient('https://api.example.com')
const res = await client.get('/users').query({ page: 1 })Clients are stored in Zustand via apiClientsSlice and accessed with the useApiClients hook:
import { useApiClients } from '@/core/clients/api.stores'
const { add, getClient, remove } = useApiClients()
const domain = 'https://api.example.com'
add(domain)
const client = getClient(domain)Releases follow Conventional Commits. Tags are pushed manually and GitHub Actions automatically publishes a GitHub Release with generated notes.
To create a new release, follow the Windsurf workflow at .windsurf/workflows/release.md.