Starter template for React apps: FSD, TypeScript, Vite. Minimal dependencies, production-ready build and Docker deploy.
- React 19 + TypeScript (strict)
- Vite 8 + @vitejs/plugin-react — dev server and build
- TanStack Router (+ devtools in dev) — routing (
app/router:root-route,routes, sharedNotFound/ErrorInterceptiondefaults) - TanStack Query — server state and cache
- Zustand — client state (in dependencies; wire stores in slices when needed)
- i18next + react-i18next — i18n (
app/providers/i18n-provider,useTranslation()in UI) - Valibot — validation (env, component props, API shapes)
- Ky — HTTP client
- SASS/SCSS + LightningCSS — styles (CSS pipeline)
- Jest + Testing Library — tests
- ESLint + Stylelint + Steiger (FSD) + Prettier + Commitlint + Husky — lint and commits
- Docker + NGINX — deploy
src/
├── app/
│ ├── layouts/ # e.g. root-layout/ (+ co-located *.schema.ts)
│ ├── providers/ # i18n-provider, query-client-provider, router-provider (kebab-case folders)
│ ├── router/ # root-route, routes, router factory
│ ├── locales/ # app-wide copy (en.json, ru.json, …)
│ └── styles/ # global SCSS entry and tokens
├── pages/ # route screens (kebab-case slices: home, not-found, error-interception, …)
├── widgets/ # cross-page blocks (e.g. navigation — header/footer)
├── features/ # user-facing flows (empty stubs ready for slices)
├── entities/ # domain pieces (empty stubs ready for slices)
└── shared/ # UI kit, api, config (validated env), lib, assets/locales
Imports only “down”: app → pages → widgets → features → entities → shared.
Slices under pages/, widgets/, entities/, and features/ use kebab-case folder names. Each slice has model/ (schemas, optional locales/ for copy, optional constants/store). Typical layout: ui/<slice>/<slice>.tsx, model/schemas/<slice>.schema.ts, optional model/locales/en.json (and other languages), and optional model/constants.ts / model/store.ts. The slice root index.ts is the public API for that slice. In app/ and shared/ui, schemas sit next to the .tsx file (no model/ under shared/ui). Domain HTTP + TanStack Query for entities/features lives in slice api/ when you add it — see AGENTS.md and .cursor/rules/api-contracts.mdc.
- Slices (pages, widgets, features, entities): put locale files in
model/locales/(en.json,ru.json, …), next tomodel/schemas/— not a separatei18n/at the slice root. - Shared kit copy:
shared/assets/locales/. App-wide defaults:app/locales/. i18n-bootstrap.tsglobs those paths (plusapp/*/model/localesfor any futureappsubfolders withmodel), deep-merges per language, registers under i18next’s defaulttranslationnamespace. In UI:useTranslation()once, dot keys (t("navigation.header.home"), …). Prefer kebab-case top-level keys in each JSON file (navigation,home, …) — seeAGENTS.mdand.cursor/rules/fsd-i18n.mdc.
Before adding code, ask yourself in order:
- App — is this app init, routing, providers, global styles, or app-wide config?
- Pages — is this a full page (screen) for one route/URL?
- Widgets — is this a large self-contained UI block used on multiple pages or one of several such blocks on one page?
- Features — is this a user action with business value, reused in different places (form, action button, flow)?
- Entities — is this a business entity from the domain (model, entity representation without binding to one action)?
- Shared — is the code not business-bound: utils, UI kit, API client, config, i18n?
The first match defines the layer. If none fit — clarify boundaries (don’t bloat entities; non-reused code can stay on the page).
- Node.js ≥ 24.12
- pnpm (recommended)
git clone <repo-url>
cd reactjs-template
pnpm install
pnpm run devThe app will open at the URL from the output.
| Command | Description |
|---|---|
pnpm run dev |
Dev server with HMR (--host) |
pnpm run build |
Production build (runs check first via prebuild) |
pnpm run build:compress |
Build + gzip/brotli for static assets |
pnpm run build:analyze |
Build + bundle size report |
pnpm run serve |
Preview production build locally |
pnpm run check |
Typecheck + lint + tests |
pnpm run test |
Jest |
pnpm run lint |
ESLint + Stylelint + Steiger (FSD) |
pnpm run lint:eslint |
ESLint only (with autofix) |
pnpm run lint:styles |
Stylelint for css / sass / scss |
pnpm run lint:fsd |
Steiger FSD boundaries |
pnpm run format |
Prettier (code and listed formats) |
pnpm run deploy |
Deploy dist to GitHub Pages |
Copy .env.example to .env and adjust if needed. Values are validated at startup with Valibot in src/shared/config/env.ts; invalid configuration throws before the app renders.
VITE_API_URL— base API URL (must be a valid URL)VITE_I18N_DEBUG—trueorfalse— i18n debug loggingVITE_I18N_FALLBACK_LNG— fallback language codeVITE_I18N_STORAGE_KEY— key for persisting language in storage
docker build -t reactjs-template .
docker run -p 80:80 reactjs-templateBuild uses build:compress; static assets are served via NGINX (config in nginx.conf).
- Strict typing, no
any - FSD: layer boundaries, import direction, Steiger in CI/local
- Component props: Valibot schemas + explicit
ComponentNameProps(seeAGENTS.mdand.cursor/rules/) - Prefer arrow functions, SOLID and KISS
- No comments or dead code in prod
Detailed rules in .cursor/rules/ and AGENTS.md.
Commits are checked with Conventional Commits (Husky + commitlint). Format:
<type>(<scope>): <subject>
- type:
feat,fix,docs,style,refactor,perf,test,chore,ci,build. - scope (optional): area of change, e.g.
auth,header,deps. - subject: short description in imperative mood, no period at end; up to 72 chars.
Examples: feat(auth): add login form, fix(api): handle 404, chore(deps): update vite.
- Email: d_maksimyk@vk.com
- Telegram: t.me/d_maximyuk
- GitHub: github.com/dmaximyuk
Template is free to use, modify and distribute.