Opinionated React starter using Vite, TanStack Router (File Routes), TanStack Query, shadcn/ui, Tailwind CSS v4, Zustand, and Bun.
This project is designed for large, scalable admin dashboards with strict structure, routing discipline, and reusable screen patterns.
This project uses Bun as both the package manager and runtime.
macOS / Linux
curl -fsSL https://bun.sh/install | bashRestart your terminal and verify:
bun --versionWindows
powershell -c "irm bun.sh/install.ps1 | iex"Restart PowerShell and verify:
bun --versionbun installCreate .env.local:
VITE_API_BASE_URL=https://<your-supabase>.supabase.co
VITE_API_KEY=<your-supabase-anon-key>
MODE=developmentbun devbun run buildsrc/
├─ main.tsx # App bootstrap (router + providers)
├─ routes.ts # Virtual route config
├─ routeTree.gen.ts # Generated – DO NOT EDIT
├─ pages/ # File-based routes
│ ├─ auth/ # Login / forgot password
│ ├─ middlewares/ # Route guards
│ ├─ app/(_authenticated)/ # Protected area
│ ├─ error/ # Error pages
│ └─ public/ # Public pages
├─ components/
│ ├─ ui/ # shadcn primitives
│ └─ screens-component/ # Screen-level UI (web/mobile)
├─ hooks/
│ ├─ apis/ # TanStack Query APIs
│ └─ store/ # Zustand stores
├─ layouts/ # App shell layouts
├─ config/ # env + request wrapper
├─ lib/utils/ # Utilities
└─ styles/ # Tailwind + theme tokens
- Uses TanStack Router + virtual routes
- Routes defined in
src/routes.ts - Generated tree in
routeTree.gen.ts(do not edit)
| Middleware | Purpose |
|---|---|
| restrict-login-signup | Prevent logged-in users from auth pages |
| authenticate | Protects authenticated routes |
| require-admin | Admin-only routes |
src/pages/app/(_authenticated)/layout.tsx
Wraps sidebar + header + <Outlet />.
-
All API calls go through
request()from:src / config / request.ts;
-
Automatically injects:
apikeyAuthorization- handles expiry + logout
-
Use TanStack Query for all data fetching.
To keep routing, screens, and APIs consistent, this project includes a screen scaffolding script.
/scripts/scaffold-screen.ts
Add to package.json:
{
"scripts": {
"new:screen": "bun scripts/scaffold-screen.ts"
}
}src/components/screens-component/<screen-name>/
├─ components/
│ └─ index.ts
├─ web-layout/
│ └─ index.tsx
├─ mob-layout/
│ └─ index.tsx
└─ index.ts
src/pages/<parent>/<screen-name>/
├─ index.tsx
└─ routes.ts
src/hooks/apis/<screen-name>/
├─ queries.ts
├─ mutations.ts
├─ type.ts
└─ index.ts
Also auto-updates:
src / hooks / apis / index.ts;export * from "./<screen-name>";bun run new:screen netskill-lp-modulesbun run new:screen assessment-performance-report --apibun run new:screen my-screen --parent=app/(_authenticated)IMPORTANT: zsh requires escaping parentheses.
bun run new:screen learning-path-report --api --parent=app/\(_authenticated\)/reportsbun run new:screen platform-customization --parent=app/\(_authenticated\)/settingsbun run new:screen my-shared-screen --no-pagesbun run new:screen my-screen --forcebun run new:screen completion-ratio \
--api \
--force \
--parent=app/\(_authenticated\)/reportszsh treats parentheses as glob patterns.
Always escape or quote paths containing:
(_authenticated)
Correct examples:
--parent=app/\(_authenticated\)/reportsor
--parent="app/(_authenticated)/reports"import { ScreenWebLayout } from "@/components/screens-component/screen/web-layout";
import { ScreenMobileLayout } from "@/components/screens-component/screen/mob-layout";
import { useIsMobile } from "@/hooks/use-mobile";
function Screen() {
const isMobile = useIsMobile();
return isMobile ? <ScreenMobileLayout /> : <ScreenWebLayout />;
}
export default Screen;| Command | Description |
|---|---|
| bun dev | Start dev server |
| bun run build | Typecheck + build |
| bun preview | Preview production build |
| bun lint | Run ESLint |
| bun run lint:fix | Auto-fix lint |
| bun run typecheck | TypeScript check |
| bun run new:screen | Scaffold new screen |
- Do not edit
routeTree.gen.ts - Always use the screen scaffold script
- Screens live in
screens-component - Pages are thin route wrappers only
- APIs must live under
hooks/apis - Prefer query + mutation hooks
- Do not place logic inside layouts