Prescriptive React + TS Greenfield Scaffolding Defaults
Sign Up Free

Preview site — latest 100 public prompts. Sign up free on markmyprompt.com for the full library.

Get started →
Priya Ranjan Singh
Priya Ranjan Singh
@crashuniverse
4d ago

Prescriptive React + TS Greenfield Scaffolding Defaults

best-practicesfrontend-engineeringreact-typescript
---
name: frontend-engineering
description: >-
  Prescriptive engineering choices for generating new (greenfield) React +
  TypeScript projects. Use when scaffolding or generating a new React/TS app,
  component library, or frontend feature set to make consistent, defensible
  decisions across architecture, type safety, state, data fetching, styling,
  routing, forms, accessibility, performance, testing, security, observability,
  and delivery. Provides named default tools with rationale and deviation
  guidance.
metadata:
  stack: React + TypeScript + Vite (SPA)
  package_manager: npm
  repo_shape: single package
  purpose: greenfield code generation
---

# Frontend Engineering Choices (React + TypeScript)

Prescriptive defaults for generating **new, single-package React + TypeScript
SPAs built with Vite**. Apply these when scaffolding a new project. Deviate only
when a project's requirements explicitly conflict — and say why when you do.

**Baseline stack:** React + TypeScript, Vite (SWC), npm, Node 24, single package,
client-rendered SPA (no SSR/SSG). Static-deployable to any Linux host.

## A. Foundation & Tooling

- Scaffold with Vite's `react-swc-ts` template. Keep its default `tsconfig`
  as-is — do **not** add custom strictness overrides.
- Build with Vite + `@vitejs/plugin-react-swc`. Production build is `vite build`
  → static assets.
- Commit `package-lock.json`; use `npm ci` in CI. Keep only Vite's default
  scripts (`dev`, `build`, `preview`) at scaffold time; other scripts arrive with
  their own aspects (lint, typecheck, test).
- Target **Node 24**. Pin via `.nvmrc` and `engines.node` in `package.json`.

## B. Language & Type Safety

- Use `interface` for object/prop/public shapes; use `type` for unions,
  intersections, and mapped types. Model variants as **discriminated unions**.
- Never use `any` — prefer `unknown` and narrow. Use `satisfies` for typed
  literals.
- Inherit Vite's default `strict: true` config. Add no custom compiler flags.
- Parse **all untrusted external data with Zod** at the boundary (API responses,
  `localStorage`, URL params, form input). Parse once at the edge, then trust the
  inferred type inward.
- Validate required `VITE_*` env vars once at startup with a Zod schema; export a
  typed `env` object. App code imports `env`, never `import.meta.env` directly.
- Zod-inferred types (`z.infer`) are the canonical API types. Do not hand-maintain
  parallel interfaces.

## C. Architecture & Project Structure

- Use a **feature-based** layout: `src/features/<feature>/` holds that feature's
  components, hooks, api, and types. Put shared cross-feature code in top-level
  `src/components/`, `src/lib/`, `src/hooks/`.
- Features must not import each other's internals. Promote shared code up instead.
- Two component layers: reusable presentational primitives in `src/components/ui`
  (no business logic), and feature components that compose primitives + logic.
  Push logic into hooks — do **not** use a container/presentational split.
- Alias `@/` → `src/` (tsconfig paths + Vite resolve). **Avoid barrel `index.ts`
  files** (tree-shaking and circular-import problems); import directly.
- Components render; custom hooks hold logic and data-fetching; pure helpers live
  in `lib/`. Wrap feature data access in `useX()` hooks.
- Code-split at the **route level** with `React.lazy` + `Suspense`. Don't split
  below routes except for genuinely heavy components (charts, editors).
- Add a dependency only when it solves a real, non-trivial problem, is
  well-maintained, and has reasonable bundle cost. Prefer platform APIs and the
  tools already prescribed here.

## D. Component Design & UI

- Function components only. Type props via `interface`.
- **Composition over configuration**: prefer `children` and sub-components over
  many boolean/config props. Avoid prop explosion.
- When wrapping native elements, use `React.ComponentProps<'button'>` etc. and
  spread rest props.
- Build primitives on **headless, accessible** behavior libraries (Radix) and
  style them in-repo.
- Use **shadcn/ui** (Radix + Tailwind) as the component system. Copy components
  into `src/components/ui` and own them in-repo.
- Prefer **uncontrolled** with sensible defaults for simple inputs; expose a
  controlled API (`value` + `onChange`) when the parent needs the value; support
  both via `value`/`defaultValue`. Forms go through Section I.
- Use the **compound-component** pattern for multi-part components, backed by a
  small local Context. Keep context scoped — it is not a substitute for global
  state (Section E).

## E. State Management

Default to local state and escalate deliberately:

```
Local (useState/useReducer)  → component-specific UI state
Lifted props                 → shared between 2-3 siblings
URL search params            → shareable/navigable state (filters, tabs, paging)
Server state (TanStack Query)→ remote data with caching
Zustand (deferred)           → cross-tree client state, only once complex
```

- Use **TanStack Query** for all server state (fetch/cache/revalidate/mutations).
  Never duplicate server data into client state.
- **Zustand** is the chosen global-state library but is **not installed by
  default**. Add it only later, when the app grows complex and local state is
  insufficient. Keep stores small and sliced by concern.
- Treat the **URL as the source of truth** for shareable state via the router's
  search-param APIs (Section H). Validate params with Zod.
- Use **React Hook Form + Zod resolver** for non-trivial forms (Section I).
- Compute derived values during render. Use `useMemo`/`useCallback` only for
  measured expensive work or referential stability — not by default. React
  Compiler is not prescribed yet.

## F. Data Layer

- Fetch with **TanStack Query over native `fetch`** (no axios). Use a thin typed
  client and expose data via feature `useX()` hooks. Structure query keys as
  arrays (`['feature', id]`).
- Default to **REST via `fetch`** with a thin wrapper (base URL, headers, JSON,
  error normalization). Validate every response with Zod at the boundary; derive
  types from `z.infer`. Use GraphQL/tRPC only when the backend uses them.
- Use TanStack Query defaults with a sensible per-query `staleTime`. Mutations
  invalidate affected keys. Use optimistic updates for high-frequency, low-risk
  actions; otherwise invalidate-and-refetch.
- Every data view handles **loading, error, empty, and success** explicitly:
  loading via Suspense/`isPending` with skeletons; errors via error boundaries
  (Section O) plus inline retry; empty states are designed, not blank.
- Use `useInfiniteQuery` for infinite lists and page-based queries for
  pagination. Prefer cursor-based pagination when the API supports it. Virtualize
  long lists (Section K). No streaming/SSR.

## G. Styling & Design

- Use **Tailwind CSS v4**, utility-first. Compose variants with `cva` +
  `tailwind-merge` + a `cn()` helper (the shadcn convention). No CSS-in-JS. Use
  plain CSS only for the global reset/base layer.
- Define design tokens as **CSS variables** consumed by the Tailwind theme, with
  semantic names (`background`, `foreground`, `primary`, `muted`). **Do not add
  dark mode by default** — light theme only.
- Respect typographic hierarchy: one `h1` per page, no skipped heading levels,
  never use heading styles for non-heading content.
- Design **mobile-first** using Tailwind's default breakpoints
  (`sm`/`md`/`lg`/`xl`/`2xl`); avoid custom breakpoints unless needed; use
  container queries where appropriate. Stay on the spacing scale — no arbitrary
  pixel values. Test at **320 / 768 / 1024 / 1440px**.
- Animate with CSS transitions / `tailwindcss-animate` utilities. No Framer
  Motion. Respect `prefers-reduced-motion`.
- Use **lucide-react** for icons. Import SVGs as components via Vite. Optimize
  raster assets (Section K). Self-host fonts (`@fontsource`/local); no
  render-blocking external font CSS.

### Avoid the AI aesthetic

Steer away from the generic AI-generated look:

- No default purple/indigo palette — use the project's real palette.
- No excessive gradients, no maximal rounding (`rounded-2xl` on everything), no
  shadow-heavy layering, no oversized uniform padding.
- No stock card grids or generic hero sections — prefer content-first,
  purpose-driven layouts.
- Use **realistic placeholder content**, not lorem ipsum — it reveals real
  length, wrapping, and overflow problems.

## H. Routing & Navigation

- Use **React Router v7** (declarative/library mode). Define routes centrally in
  a typed route config.
- Group route definitions by feature; lazy-load each route's page component with
  `React.lazy`. A central router module composes feature routes.
- Use nested layout routes for shared shells (nav/sidebar). Gate auth with a
  `ProtectedRoute` wrapper/layout that checks session and redirects to login,
  preserving the intended destination.
- Load data with **TanStack Query in components/hooks, not router loaders**
  (single data layer). Optionally prefetch on navigation intent (hover/focus).

## I. Forms & Validation

- Use **React Hook Form** for all non-trivial forms. Plain `useState` is fine for
  trivial single-input cases (e.g. a search box). Integrate with shadcn/ui `Form`
  components.
- Use **Zod** schemas as the single source of truth via `@hookform/resolvers`
  `zodResolver`. The same schema drives types (`z.infer`), client validation, and
  boundary parsing. No separate validation logic.
- Accessible forms: associate labels via `htmlFor`/`id`; link errors with
  `aria-describedby` + `aria-invalid`; validate on submit and revalidate on change
  after the first error; focus the first invalid field on submit; show inline
  error text. shadcn/ui `Form` provides most of this wiring.

## J. Accessibility (WCAG 2.1 AA)

- Use semantic elements first (`button`, `nav`, `main`, ordered headings). Use
  ARIA only to fill gaps, never to replace semantics. All interactive elements are
  real controls — no clickable `div`s. Radix primitives give correct roles/ARIA.
- Everything must be keyboard-operable with visible focus rings (never remove an
  outline without a replacement). Trap focus in dialogs/menus (Radix handles it),
  return focus to the trigger on close, and provide a skip-to-content link.
- Meet WCAG 2.1 AA contrast (4.5:1 text, 3:1 large text/UI). Never rely on color
  alone — pair with icon or text. Respect `prefers-reduced-motion`.
- Check a11y with `eslint-plugin-jsx-a11y` (static) and axe (`@axe-core/react` in
  dev, axe assertions in tests). Do a manual keyboard + screen-reader smoke test
  of key flows.

## K. Performance

- Analyze bundles with `rollup-plugin-visualizer` on demand. Keep initial route
  JS lean via route-level splitting. Prefer lighter deps. Treat budgets as soft
  guidance, not hard CI gates.
- Control re-renders through component structure and stable props first; memoize
  only when measured. Virtualize long lists (`@tanstack/react-virtual`) beyond
  ~100 rows. Avoid inline object/array literals in hot paths.
- Targets: **LCP < 2.5s, INP < 200ms, CLS < 0.1**. Reserve space for async
  content, prioritize above-the-fold, keep the main thread free. Measure with the
  `web-vitals` lib.
- Optimize assets: responsive images with explicit `width`/`height`; lazy-load
  below-the-fold (`loading="lazy"`); modern formats (WebP/AVIF); self-hosted fonts
  with `font-display: swap` and a preload for the critical font. Vite hashes
  assets.
- Loading strategy: route-level lazy + `Suspense` with skeletons; prefetch on
  navigation intent; `<link rel="preload">` for critical assets.

## L. Testing

- Use **Vitest** (native Vite integration). Test behavior, not implementation.
  Unit-test pure logic (`lib/`, hooks, schemas). Colocate tests as `*.test.ts(x)`
  next to source.
- Component/integration tests use **React Testing Library** + `user-event`, DOM
  via **jsdom**. Query by role/label. Integration tests over feature flows are the
  primary value.
- **Playwright** is the chosen E2E tool but is **not installed by default**. Defer
  it until a project warrants E2E, then keep tests minimal (a few high-value smoke
  paths).
- No dedicated visual-regression tooling by default. Add accessibility assertions
  via jest-axe/axe inside RTL tests for key components.
- Mock the network at the boundary with **MSW** for component/integration tests.
  Use typed test-data factories that reuse Zod schemas so fixtures stay valid.
- No hard coverage %. Prioritize business logic, hooks, forms/validation, and
  critical flows. Don't test trivial markup or third-party behavior. Use Vitest
  `--coverage` (v8) for visibility, not as a gate.

## M. Quality, Linting & Formatting

- Use **ESLint** (flat config): Vite's default TS-ESLint setup plus
  `eslint-plugin-react-hooks`, `eslint-plugin-react-refresh`, and
  `eslint-plugin-jsx-a11y`. Stay close to recommended presets; no heavy custom
  rules.
- Use **Prettier** with `prettier-plugin-tailwindcss` for class sorting. ESLint
  owns code-quality, Prettier owns formatting (`eslint-config-prettier` removes
  overlap).
- **No pre-commit hooks.** Do not add Husky, lint-staged, or any git hooks. Rely
  on the editor and manual checks.
- Type-check with `tsc --noEmit` as a `typecheck` script. The standard local check
  sequence is **lint → typecheck → test → build** (run manually; CI is off by
  default — this is what a CI job would run if one were added).

## N. Security

- Rely on React's default escaping. Avoid `dangerouslySetInnerHTML`; if
  unavoidable, sanitize with **DOMPurify**. Never build HTML from strings. Validate
  all external data with Zod. Avoid `eval` and dynamic code.
- Ship a strict **Content Security Policy** plus standard security headers
  (`X-Content-Type-Options`, `Referrer-Policy`, HSTS), set at the static host/CDN
  (Section R).
- Prefer **httpOnly, Secure, SameSite cookies** (set by the backend) over
  `localStorage` for auth tokens. If a token must live in JS, keep it in memory
  only. Never store secrets or PII in `localStorage`.
- Run `npm audit` for visibility. Keep dependencies current and minimal. Pin via
  the lockfile. Dependabot/renovate is optional.
- `VITE_*` vars are **public by definition** — never put secrets in them. Real
  secrets stay server-side. Gitignore `.env` and commit a `.env.example`.

## O. Error Handling & Observability

- Use **react-error-boundary** at the app root and around lazy routes and risky
  subtrees, with a friendly fallback offering reset/retry. TanStack Query handles
  data errors inline; boundaries catch render errors.
- **Sentry** is the chosen error-reporting tool but is **not installed by
  default**. Add it when production monitoring is needed. Until then, log to
  console in dev with no noisy prod logging.
- No analytics by default. When needed, prefer lightweight, privacy-friendly
  options (Plausible/Umami) over heavy trackers.
- Measure Core Web Vitals locally with `web-vitals`. Report only if
  analytics/Sentry are enabled. No dedicated RUM service by default.

## P. Internationalization & Content

- **i18n is off by default** — build single-locale (English) and defer install.
  `react-i18next` is the chosen tool when needed. Even without i18n, don't bury
  hardcoded user-facing strings in ways that block later extraction.
- Use native `Intl` APIs (`Intl.DateTimeFormat`, `Intl.NumberFormat`) for
  date/number/currency formatting even single-locale. No heavy date libs; use
  `date-fns` only if date math is needed.
- No RTL support by default (single LTR locale). Use logical CSS / Tailwind logical
  utilities where trivial so RTL isn't blocked later.

## Q. SEO & Metadata

- `react-helmet-async` is the chosen head-management tool but is **deferred**.
  Rely on a good static `<head>` in `index.html` (title, description, viewport,
  charset). Add it only when per-route meta is actually needed.
- Add Open Graph + Twitter Card meta in `index.html` for link previews when
  relevant. Use JSON-LD only when the app is content/SEO-relevant.
- **Sitemap/robots are OFF by default.** Do not scaffold `sitemap.xml` or
  `robots.txt`. Add them only for an explicitly public, crawlable site.

## R. DX, CI/CD & Delivery

- Use plain **npm scripts** (no task runner): `dev`, `build`, `preview`, plus
  `lint`, `format`, `typecheck`, `test` as their sections introduce them. No
  aggregate `check` script.
- **No CI by default.** Do not scaffold GitHub Actions or workflow files. Add CI
  only when a project explicitly needs it.
- Deployment is **host-agnostic** — no default host. Every SPA builds to static
  assets deployable on any Linux host. Configure SPA fallback (all routes →
  `index.html`) and the Section N security headers/CSP at the host.
- Manage env with Vite mode files (`.env`, `.env.production`, `.env.local`). Only
  `VITE_*` is exposed, validated via Section B. Commit `.env.example`. No secrets
  client-side.
- Ship a solid **README** (setup, scripts, structure, decisions). **No Storybook
  by default.** Lightweight ADRs are optional for notable decisions.
- No versioning/changelog ceremony for app projects (continuous deploy). Use
  changesets/semver only for a publishable library. Rely on git history.

## S. Conventions & Collaboration

- Naming: components in PascalCase files (`UserCard.tsx`); hooks `useX.ts`
  camelCase; non-component modules camelCase (`formatDate.ts`); types/interfaces
  PascalCase; constants `UPPER_SNAKE`. One component per file (barrel-free).
  Feature folders kebab-case (`user-profile/`).
- Git: trunk-based with short-lived feature branches off `main`. Use
  **Conventional Commits** (`feat:`, `fix:`, `chore:`…) as a recommended
  convention (not hook-enforced). Branches: `feature/…`, `fix/…`.
- PRs: small and focused with clear descriptions. Self-review that
  lint/typecheck/test pass locally. No CODEOWNERS by default.
- Editor: ship `.editorconfig` and `.vscode/extensions.json` recommending ESLint,
  Prettier, and Tailwind CSS IntelliSense; `.vscode/settings.json` for Prettier
  format-on-save.
0 likes0 comments

Want to like, comment or save this prompt?

Sign up free to interact, create and organise your own AI prompts.

Get Started Free
Prescriptive React + TS Greenfield Scaffolding Defaults | Mark My Prompt