Opinionated rule set for designing, configuring, and maintaining modern front-end build systems focused on Vite / esbuild with TypeScript and React-centric stacks. Covers architecture, language usage, error handling, performance, testing, CI/CD, and security.
Your build system should be invisible when it works and obvious when it breaks. These Cursor Rules transform chaotic frontend builds into a predictable, fast development experience that scales with your team.
Frontend builds have become complexity monsters. Teams waste hours debugging webpack configs, fighting TypeScript path resolution, and watching 2-minute build times kill their flow state. Meanwhile, different developers get different results locally, and CI mysteriously fails on code that "works on my machine."
The specific problems crushing productivity:
These rules establish a declarative, type-safe build pipeline that optimizes for developer velocity while maintaining production reliability. Built around Vite/esbuild with comprehensive TypeScript and React support.
Core philosophy: Fast feedback, fail fast, one source of truth per concern.
// vite.config.ts - Single source of truth
export default defineConfig({
plugins: [react(), tsconfigPaths(), svgr()],
build: {
target: 'es2020',
chunkSizeWarningLimit: 500, // Block >500KB chunks
rollupOptions: {
output: { manualChunks: { react: ['react', 'react-dom'] } }
}
}
});
Enforced performance budgets: Automatic build failures for bundles >200KB gzipped
Runtime environment validation: Zod schemas validate all env vars before app boot
Strict TypeScript everywhere: Config files in .ts, noUncheckedIndexedAccess enabled
@/* resolves to src/ across all tools// Relative import hell
import { Button } from '../../../components/ui/Button';
import { useAuth } from '../../../../hooks/useAuth';
// Runtime env access (breaks at build time)
const API_URL = process.env.REACT_APP_API_URL;
// Clean path aliases
import { Button } from '@/components/ui/Button';
import { useAuth } from '@/hooks/useAuth';
// Validated env with types
import { env } from '@/config/env'; // Zod-validated at startup
// Automatic code splitting per route
const HeavyPage = lazy(() => import('./pages/Heavy'));
// Build fails if chunk exceeds 200KB
// Rollup visualizer runs on every PR
// env.d.ts - Single source of truth
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_APP_VERSION: string;
}
// Runtime validation
const envSchema = z.object({
VITE_API_URL: z.string().url(),
VITE_APP_VERSION: z.string().min(1)
});
Copy the complete rule configuration into your Cursor settings. The rules automatically configure:
src/
├─ app/ # Next.js routes
├─ components/ # UI components (PascalCase files)
├─ features/ # Business logic + state
├─ hooks/ # Custom hooks (useX.ts pattern)
├─ utils/ # Pure functions
└─ assets/ # Vite-processed static files
// package.json scripts
{
"dev": "vite --port 5173 --strictPort",
"build": "tsc && vite build",
"test": "vitest --coverage.thresholds.statements=95",
"analyze": "rollup-plugin-visualizer"
}
// lighthouse.config.js
module.exports = {
ci: {
collect: { numberOfRuns: 3 },
assert: {
assertions: {
'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }]
}
}
}
};
Teams report spending 80% less time on build configuration and 60% less time debugging environment issues. The strict performance budgets prevent technical debt accumulation, while automated testing catches regressions before they reach production.
Bottom line: These rules eliminate the build system as a productivity bottleneck, letting you focus on building features instead of fighting tooling.
You are an expert in JavaScript (ES2023+), TypeScript (5.x), Vite, esbuild, Webpack, React 18+, Next.js 14+, Cypress, Jest, and modern CI/CD.
Key Principles
- Optimize for fast feedback: <100 ms HMR reload, <30 s clean build, <10 kb critical path JS after gzip.
- Prefer native ESM and browser standards first; add polyfills only for officially supported browsers.
- Keep the build pipeline declarative: describe what, not how—let the toolchain handle orchestration.
- One source of truth per concern (env, paths, aliases, browserslist, etc.). No duplicated config.
- Type safety everywhere: all config files are written in `ts` when supported (`vite.config.ts`).
- Fail fast & noisy: the build must exit non-zero on any warning promoted to error (lint, type, test, a11y).
- Infrastructure-as-code: every local build step has an analogue in CI; no “works on my machine.”
JavaScript / TypeScript
- Default to `.ts` / `.tsx`; allow `.js` only for tool scripts; forbid implicit `any` via `strict: true` and `noUncheckedIndexedAccess`.
- Use top-level `async/await`; disallow `.then` chains outside isolated util functions.
- Prefer named exports; allow default export only for React components that match file name.
- Side-effect imports must be annotated with `/* @vite-ignore */` comment to make intent explicit.
- Use `import type { … }` for pure types to enable tree-shaking.
- Directory convention:
src/
├─ app/ # Next.js (pages or app router)
├─ components/ # presentational, PascalCase file names
├─ features/ # Redux Toolkit slices/Zustand stores
├─ hooks/ # custom hooks, `useX.ts`
├─ utils/ # pure functions, no DOM
└─ assets/ # images, fonts processed by Vite
Error Handling and Validation
- Validate `process.env`/import.meta.env at runtime with zod before application boot; throw explicit `ConfigError` if validation fails.
- In tool scripts use `process.on('unhandledRejection', fail)`; never silently swallow build errors.
- Wrap asynchronous build hooks (e.g., `configureServer`) in try/catch and re-throw ViteError with contextual message.
- Enforce max 2 nested `try` blocks per file; extract helper functions otherwise.
- Provide fallback UI for dynamic imports using React.Suspense + skeleton component.
Vite (Primary Build Framework)
- Use `vite.config.ts` with defineConfig:
```ts
export default defineConfig({
plugins: [react(), tsconfigPaths(), svgr()],
build: {
target: 'es2020',
cssTarget: 'chrome90',
sourcemap: process.env.GENERATE_SOURCEMAP === 'true',
minify: 'esbuild',
chunkSizeWarningLimit: 500, // kB
rollupOptions: {
output: { manualChunks: { react: ['react', 'react-dom'] } },
},
},
server: { open: true, port: 5173, strictPort: true },
preview: { port: 4173 },
});
```
- Always enable `tsconfigPaths()` to keep import aliases consistent.
- Declare env variables in `env.d.ts` and `.env.example`; never read raw `process.env` in source code.
- Code-split per route: `import('./pages/Heavy.tsx')` + `React.lazy`.
- Use dynamic `import.meta.glob(['./pages/**/[a-z]*.tsx'])` for file-system routing when not on Next.js.
- Integrate PWA via `@vite-pwa/plugin` only if offline support is a core requirement; disable precache for >5 MB assets.
- Enable `build.ssr` false by default; switch to true only for adapter libraries.
Next.js (Secondary Framework)
- Use `app/` router; opt-in to `serverComponents` by default; mark client components explicitly with `'use client'`.
- Turn on `experimental: { typedRoutes: true }`.
- Image optimization:
- Use `<Image>` with `sizes` attr; forbid raw `<img>` for remote sources.
- Set `output: 'standalone'` for Docker deploys.
- Configure gzip + brotli via `next-compress` plugin.
Webpack (Legacy Projects)
- Lock to v5; use `md4` -> `hashFunction: 'xxhash64'` for faster builds.
- Disable `devtool` in production unless `ANALYZE_BUILD` is set.
- Prefer `esbuild-loader` for TS/JS; limit `babel-loader` to JSX transforms.
Additional Sections
Testing
- Mandatory coverage thresholds: statements 95 %, branches 90 %, lines 95 %.
- Write tests alongside code (`*.test.ts(x)` next to implementation).
- Use `vitest` in Vite projects, `jest` in Next.js; share jest config via `@org/config-jest`.
- For E2E use Cypress component testing + Playwright for cross-browser validation.
- Mock network with MSW; forbid global `fetch` mocking.
Performance
- Enable `@vitejs/plugin-legacy` only for browsers with >1 % target share.
- Run `npm run analyze` (rollup visualizer) on every PR; block merge if any chunk >200 kB (gzipped) unless `PERF_EXEMPT` label.
- Enforce Web-Vitals budgets in CI using `lhci autorun`.
- Use `squoosh-cli` for lossless image compression in pre-commit hook.
Security
- Use SRI hashes for external CDN resources.
- Run `npm audit --production` and `socket scan` in CI; fail on `high` severity.
- For React/Next, set strict CSP via `helmet` (nonce per request).
- Sanitize user-generated HTML with DOMPurify; never dangerouslySetInnerHTML without sanitize.
CI/CD
- Pipeline stages: lint → type-check → unit-test → build → e2e-test → deploy.
- Cache `node_modules`, `.vite`, `~/.pnpm-store` keyed by lockfile hash.
- Produce immutable artifacts: `dist/` zipped with commit SHA and uploaded to artifact store.
- Use `turbo run build --filter={scope}` for monorepos to avoid full rebuilds.
Tooling & DX
- EditorConfig and Prettier enforce style; run via pre-commit.
- ESLint config extends `eslint:recommended`, `plugin:react/recommended`, `@typescript-eslint/recommended`, `prettier`.
- Path alias `@/*` resolves to `src/`; disallow relative imports that traverse above `src/` (`no-internal-relative-import` rule).
- Enable VS Code `typescript.tsserver.experimental.enableProjectDiagnostics` for in-editor errors.
Common Pitfalls & Guards
- Avoid default wildcard SSR externalization (`noExternal`)—explicit allowlist only.
- Never commit generated files (`dist`, `coverage`, `.next`, `static`)—guard via `.gitignore` & pre-push hook.
- If using CSS-in-JS, extract critical CSS at build time (`styled-components` + `babel-plugin-styled-components` or `vanilla-extract`).
- Do not rely on implicit polyfills; if adding core-js, tree-shake with `usage-pure`.
---
This rule set must be applied to every new or refactored front-end build system moving forward. Non-compliant code is rejected in code review.