Live demo: react-starter.mcshinsky.net • Storybook: react-starter.mcshinsky.net/storybook
Production-ready, opinionated Vite + React 19 + TypeScript starter with Tailwind CSS 4, TanStack Query, Vitest, and Storybook 8. Built for teams shipping real products — not hello-world demos.
| Concern | create-vite |
Typical Starter | react-starter |
|---|---|---|---|
| Routing | ❌ Manual | ✅ Often | ✅ React Router 7, lazy routes, Suspense |
| Server state | ❌ Manual | ✅ TanStack Query 5, pre-configured provider | |
| Env validation | ❌ Manual | ✅ Zod schema, fails fast at boot | |
| Error boundaries | ❌ Manual | ❌ Rarely | ✅ Resettable boundary wrapping router |
| Feature folders | ❌ Flat | ✅ Enforced: features/, shared/, app/ |
|
| Testing | ✅ Vitest + RTL + jsdom, coverage gates | ||
| Storybook | ❌ Manual | ❌ Rarely | ✅ Pre-wired with Vite, docs + test addons |
| Multi-env config | ❌ Manual | ✅ .env.[mode] + Zod, 4 envs included |
|
| Path aliases | ❌ Manual | ✅ @/, @features/, @shared/ pre-configured |
TL;DR: You get ~40 hours of "set up the boring stuff right" work done for you. Fork → npm run dev → build features.
# 1. Clone and install
git clone https://github.com/michaelmcshinsky/react-starter my-app
cd my-app
npm install
# 2. Configure environment (pick one)
cp .env.development.example .env # local API on localhost
# cp .env.staging.example .env # staging API
# cp .env.production.example .env # production API
# 3. Start developing
npm run devThat's it. Open http://localhost:5173 — you have a running app with routing, server state, env validation, error boundaries, and a test suite.
src/
app/ # App shell: router, providers, global styles, error boundary
features/ # Feature modules — one folder per feature (home, about, auth, etc.)
home/ # components/, hooks/, api.ts, types.ts, index.ts (public API)
about/
shared/ # Cross-cutting: config (Zod env), providers, UI primitives, utilities
test/ # Test setup, utilities, custom render
| Decision | Rationale |
|---|---|
| Feature folders | Colocates route-level code with its components, hooks, types, and tests. Scales better than pages/ + components/ split as features grow. |
Path aliases (@/, @features/, @shared/) |
Eliminates ../../../ imports; makes refactoring safe; works in Vitest + Storybook + Vite identically. |
Route-level code splitting (React.lazy + Suspense) |
Default for every feature route. Keeps initial bundle small; loading UX handled by shared Suspense fallback. |
| Error boundary wrapping router | Catches render errors in any route, shows resettable fallback, logs to error service. Prevents white-screen crashes. |
Zod-validated import.meta.env |
Fails at boot (not runtime) if required vars missing or malformed. Type-safe env object everywhere via shared/config. |
| TanStack Query as server-state layer | Separates server state from client state. Devtools, caching, deduping, retries, prefetching — all configured once in shared/providers. |
Tailwind CSS 4 via @tailwindcss/vite |
Zero-config, lightning-fast, native Vite integration. No PostCSS config needed. |
| Vitest + Testing Library + jsdom | Fast, React-native testing. shared/test/render.tsx wraps providers so every test gets QueryClient, Router, Theme. |
- Home — Hero, feature grid, navigation to About
- About — Static content page demonstrating lazy route loading
- Error Boundary Demo — Trigger a render error to see the fallback in action
| Pattern | Location | Use For |
|---|---|---|
| Auth stub | features/auth/ (create it) |
Login/register forms, protected routes, token refresh |
| Data table | shared/components/DataTable.tsx |
Sortable, filterable, paginated server-driven tables |
| Zod form | shared/components/ZodForm.tsx |
Type-safe forms with schema validation + error display |
| Error handling | shared/lib/errors.ts |
ApiError, ValidationError, NotFoundError classes + handleError helper |
| API client | shared/api/client.ts |
Typed fetch wrapper with interceptors, auth, base URL from env |
Pro tip: Run
npm run storybookto browse all shared components in isolation with controls and docs.
| Layer | Choice | Version |
|---|---|---|
| Build | Vite | 6 |
| UI | React + TypeScript (strict) | 19 |
| Routing | React Router (lazy routes) | 7 |
| Styling | Tailwind CSS (@tailwindcss/vite) |
4 |
| Server State | TanStack Query | 5 |
| Config Validation | Zod | 3 |
| Testing | Vitest + Testing Library + jsdom | 2 |
| Component Explorer | Storybook | 8 |
| Linting | ESLint 9 flat + typescript-eslint | 9 |
| Package Manager | npm (works with pnpm/yarn) | — |
| Command | Description |
|---|---|
npm run dev |
Start Vite dev server (HMR, env validation) |
npm run build |
Typecheck (tsc --noEmit) + production build |
npm run preview |
Preview production build locally |
npm test |
Run Vitest suite (watch mode) |
npm run test:run |
Run Vitest once (CI mode) |
npm run test:coverage |
Run tests with V8 coverage report |
npm run typecheck |
tsc --noEmit |
npm run lint |
ESLint (flat config) |
npm run storybook |
Storybook dev server (port 6006) |
npm run build-storybook |
Static Storybook build to storybook-static/ |
Vite loads .env.[mode] automatically based on --mode flag. All VITE_* keys are validated by Zod at boot in shared/config/env.ts.
| File | Purpose |
|---|---|
.env.development.example |
Local dev — API on http://localhost:3000 |
.env.staging.example |
Staging — API on https://api-staging.example.com |
.env.production.example |
Production — API on https://api.example.com |
# Development (default)
npm run dev
# Staging
npm run dev -- --mode=staging
# Production build
npm run build -- --mode=productionRequired env vars (validated at boot):
VITE_API_URL=https://api.example.com
VITE_APP_NAME=My App# 1. Create feature folder
mkdir -p src/features/my-feature/{components,hooks}
# 2. Add route (lazy-loaded) in src/app/router.tsx
{ path: '/my-feature', element: <LazyMyFeature /> }
# 3. Write components, hooks, types, API calls
# 4. Add tests in src/features/my-feature/__tests__/
# 5. Add stories in src/features/my-feature/*.stories.tsx- Create
src/features/auth/withLoginForm,RegisterForm,useAuthhook - Add
AuthProviderinshared/providers/AuthProvider.tsxwrappingQueryClientProvider - Protect routes with
<RequireAuth>wrapper component - Add token refresh logic in
shared/api/client.tsinterceptors
# Static hosting (Vercel, Netlify, Cloudflare Pages, Surge)
npm run build
# Deploy dist/ folder
# Docker
docker build -t my-app .
docker run -p 8080:80 my-app- Architecture Deep-Dive — Why feature folders, how TanStack Query integrates, error boundary patterns
- Decisions Log — Every major choice with tradeoffs and alternatives considered
- Contributing — Branch strategy, commit conventions, PR requirements
- Security — Vulnerability reporting, dependency policy
Issues and PRs welcome! Please read CONTRIBUTING.md first. Key points:
- All changes require passing
typecheck,lint,test, andbuild - Feature work on branches; PRs target
main - Update stories/tests/docs alongside code
MIT © Michael McShinsky — see LICENSE for details.