The frontend ecosystem is chaotic. Unlike the server-side where a crash is logged and the server restarts, a frontend crash leaves the user staring at a blank white screen.
You are likely already using Prettier for code style. But Prettier is just makeup; it doesn't stop your application from leaking memory or crashing due to undefined props.
To build a truly stable applicationβwhether it is a modern React/Vue SPA or a legacy jQuery monolithβyou need to enforce strict types, architectural boundaries, and performance rules automatically.
Here is how to set up a "Frontend Fortress" that catches bugs before they hit the browser.
1. Static Analysis: The First Line of Defense
The Problem: JavaScript is loosely typed. You pass a string where a number is expected, and the app runs fine until a specific user action crashes it.
The Solution: Strict TypeScript & ESLint.
TypeScript (Strict Mode)
TypeScript is your version of "Larastan." But it only works if you turn the safety features on.
Implementation:
Ensure your tsconfig.json has the strictest settings enabled. This forces you to handle null and undefined explicitly.
// tsconfig.json
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUncheckedIndexedAccess": true // Vital: Prevents accessing array[10] without checking if it exists
}
}
ESLint (The Logic Police)
ESLint catches bad patterns, like missing dependency arrays in Hooks or accidental global variables in legacy code.
Implementation:
Install strict plugins for your specific stack.
npm install eslint eslint-plugin-vue eslint-plugin-react-hooks eslint-plugin-jquery --save-dev
Configure .eslintrc.json (Multi-Stack Support):
{
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:vue/vue3-recommended", // For Vue
"plugin:react-hooks/recommended" // For React
],
"rules": {
"no-var": "error", // For legacy: Kill "var", force const/let
"eqeqeq": "error", // For legacy: Force === over ==
"vue/multi-word-component-names": "off",
"react/jsx-no-bind": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }]
},
"overrides": [
{
"files": ["**/*.js", "**/*.jquery.js"],
"env": { "jquery": true } // Enable jQuery globals only for legacy files
}
]
}
2. Enforce Architecture: Dependency Cruiser
The Problem: "Import Hell." A junior developer imports a heavy API service directly into a UI button, or creates circular dependencies that break the build.
The Solution: Dependency Cruiser.
This works for React, Vue, and Vanilla JS. It validates who is allowed to import whom.
Implementation:
npm install --save-dev dependency-cruiser
Create .dependency-cruiser.js. Here is a rule that enforces a clean flow: Components can use Logic/Stores, but Components cannot call the API directly.
module.exports = {
forbidden: [
{
"name": "no-components-importing-api",
"comment": "UI components must use Hooks/Composables. Do not call API directly.",
"from": { "path": "^src/(components|views)" },
"to": { "path": "^src/api" }
},
{
"name": "no-legacy-mixing",
"comment": "Modern Vue/React code should not import legacy jQuery scripts.",
"from": { "path": "^src/(components|features)" },
"to": { "path": "^src/legacy" }
},
{
"name": "no-circular",
"severity": "error",
"from": {},
"to": { "circular": true }
}
]
};
3. Git Hooks: The Gatekeeper
The Problem: You configure these tools, but developers forget to run them.
The Solution: Husky + Lint Staged.
We run checks only on the files changed in the current commit.
Implementation:
-
Install:
npm install husky lint-staged --save-dev npx husky init -
Configure
package.json:
"lint-staged": { "**/*.{ts,tsx,vue,js}": [ "prettier --write", // Format code "eslint --fix" // Fix logic errors ], "**/*.{ts,tsx,vue}": [ "bash -c 'tsc --noEmit'" // Strict type check (add vue-tsc for Vue) ] } -
Update
.husky/pre-commit:
npx lint-staged
Now, if a developer tries to commit a broken Vue component or a legacy script with syntax errors, the commit is blocked.
4. The Brain: AI Instructions (The Most Important Part)
The Problem: AI assistants defaults to writing "Tutorial Code." It writes Mixins for Vue (deprecated), Class Components for React (outdated), or spaghetti jQuery for legacy.
The Solution: Custom Instructions.
Create a .github/copilot-instructions.md file. This forces Copilot to act like a Senior Architect for your specific stack.
Copy and paste these exact rules into that file:
# Senior Frontend Architect Instructions
You are a Senior Frontend Architect. You prioritize performance, type safety, and maintainability. Determine the technology stack of the current file (React, Vue, or Legacy JS) and apply the specific rules below.
## π’ REACT GUIDELINES
### 1. Separation of Concerns
* **No Logic in JSX:** Move complex logic, data fetching, or transformation into Custom Hooks.
* **Structure:** `Component` -> `Custom Hook` -> `API Layer`.
### 2. Performance
* **Derived State:** NEVER use `useEffect` to update state based on props. Use `useMemo` or calculate during render.
* **Referential Integrity:** Do not pass inline objects/functions to memoized components.
### 3. Anti-Patterns
* β No `default export`. Use named exports.
* β No prop drilling (> 2 levels). Use Composition or Context.
* β No `any` type. Define strict interfaces.
---
## π΅ VUE.JS GUIDELINES (Vue 3+)
### 1. Script Setup & Composition API
* **ALWAYS** use `<script setup lang="ts">`.
* **NEVER** use the Options API (`data`, `methods`, `computed` object syntax).
* **NEVER** use Mixins. Use Composables (`useFeature`) instead.
### 2. Reactivity
* Use `ref` for primitives and `reactive` for distinct objects.
* Destructure props using `const { propName } = toRefs(props)` if you need to retain reactivity in composables.
### 3. State Management
* Avoid "Prop Drilling." Use **Pinia** for global state.
* Avoid "Event Bubbling" up multiple layers. Use `defineEmits` strictly typed.
### 4. Style Guide
* **Multi-word names:** All component files must be multi-word (e.g., `UserCard.vue`, not `Card.vue`).
* **Directives:** Use shorthands (`@click` instead of `v-on:click`, `:src` instead of `v-bind:src`).
---
## π LEGACY JAVASCRIPT & JQUERY GUIDELINES
### 1. Global Namespace Protection
* **NEVER** declare variables in the global scope.
* Wrap all code in an **IIFE** (Immediately Invoked Function Expression) or use ES Modules if the build system permits.
```
javascript
(function($) {
// Code goes here
})(jQuery);
```
### 2. DOM Performance (Cache your Selectors)
* **NEVER** query the DOM inside a loop or repeated event.
* **ALWAYS** cache selectors in variables prefixed with `$`.
* β Bad: `$('.btn').click(function() { $('.content').show(); })`
* β
Good:
```
javascript
const $btn = $('.btn');
const $content = $('.content');
$btn.on('click', () => $content.show());
```
### 3. Event Delegation
* **NEVER** bind events to elements that don't exist yet (dynamic content).
* **ALWAYS** use event delegation bound to a static parent.
* β
`$(document).on('click', '.dynamic-button', function() { ... })`
### 4. Modernization
* Use `const` and `let`. **NEVER** use `var`.
* Use Template Literals (\`variable: ${x}\`) instead of string concatenation.
* Use `Array.forEach`, `map`, `filter` instead of `for` loops where possible.
---
## π DIRECTORY STRUCTURE
### Feature-Based (React/Vue)
Group files by "feature" (business domain), not technical type.
src/
βββ features/
β βββ auth/
β β βββ components/ # LoginForm.vue / LoginForm.tsx
β β βββ hooks/ # useAuth.ts
β β βββ store/ # authStore.ts (Pinia/Zustand)
β β βββ api/ # authApi.ts
### Legacy Structure
Separate scripts clearly.
src/
βββ legacy/
β βββ modules/ # Isolated functional modules
β β βββ cart.module.js
β β βββ ui.module.js
β βββ main.js # Entry point
## π§ͺ TESTING STANDARDS (Vitest / RTL)
* **Prioritize User Behavior:** Test what the user sees, not internal state.
* **Selectors:** Use `getByRole` (accessibility) > `getByText`. Avoid `querySelector` in tests.
* **Mocking:** Mock network requests (MSW), not business logic functions.
Conclusion
By implementing this pipeline, you stop fighting fires and start building features.
- TypeScript/ESLint prevents crashes across all frameworks.
- Dependency Cruiser stops spaghetti code in both SPAs and Legacy apps.
- Husky enforces the rules before commit.
- Copilot is now trained to write modern React, clean Vue, and disciplined Legacy JS.
This is how you scale a frontend codebase without it collapsing under its own weight.
Top comments (0)