| name | frontend-js-best-practices |
| description | JavaScript performance optimization guidelines. Use when writing, reviewing, or refactoring JavaScript/TypeScript code to ensure optimal performance patterns. Triggers on tasks involving loops, data structures, DOM manipulation, or general JS optimization. |
JavaScript Best Practices
Performance optimization and code style patterns for JavaScript and TypeScript code. Contains 17 rules focused on reducing unnecessary computation, optimizing data structures, and maintaining consistent conventions.
When to Apply
Reference these guidelines when:
- Writing loops or array operations
- Working with data structures (Map, Set, arrays)
- Manipulating the DOM directly
- Caching values or function results
- Optimizing hot code paths
- Declaring variables or functions
Rules Summary
const-let-usage (MEDIUM) — @rules/const-let-usage.md
Use const at module level, let inside functions.
const MAX_RETRIES = 3;
const userCache = new Map<string, User>();
function process(items: Item[]) {
let total = 0;
let result = [];
for (let item of items) {
total += item.price;
}
return { total, result };
}
function-declarations (MEDIUM) — @rules/function-declarations.md
Prefer function declarations over arrow functions for named functions.
function calculateTotal(items: Item[]): number {
let total = 0;
for (let item of items) {
total += item.price;
}
return total;
}
let active = users.filter((u) => u.isActive);
const handler: ActionFunction = async ({ request }) => {
};
no-default-exports (MEDIUM) — @rules/no-default-exports.md
Use named exports. Avoid default exports (except Remix route components).
export default function formatCurrency(amount: number) { ... }
export function formatCurrency(amount: number) { ... }
export default function Component() { ... }
no-as-type-casts (HIGH) — @rules/no-as-type-casts.md
Avoid as Type casts. Use type guards or Zod validation instead.
let user = response.data as User;
let user = UserSchema.parse(response.data);
if (isUser(response.data)) {
let user = response.data;
}
comments-meaningful-only (MEDIUM) — @rules/comments-meaningful-only.md
Only comment when adding info the code cannot express.
let userName = user.name;
if (transaction.amount < 250) {
return { requiresAcknowledgment: false };
}
set-map-lookups (LOW-MEDIUM) — @rules/set-map-lookups.md
Use Set/Map for O(1) lookups instead of Array methods.
const allowedIds = ["a", "b", "c"];
items.filter((item) => allowedIds.includes(item.id));
const allowedIds = new Set(["a", "b", "c"]);
items.filter((item) => allowedIds.has(item.id));
index-maps (LOW-MEDIUM) — @rules/index-maps.md
Build Map once for repeated lookups.
orders.map((order) => ({
...order,
user: users.find((u) => u.id === order.userId),
}));
const userById = new Map(users.map((u) => [u.id, u]));
orders.map((order) => ({
...order,
user: userById.get(order.userId),
}));
tosorted-immutable (MEDIUM-HIGH) — @rules/tosorted-immutable.md
Use toSorted() instead of sort() to avoid mutation.
const sorted = users.sort((a, b) => a.name.localeCompare(b.name));
const sorted = users.toSorted((a, b) => a.name.localeCompare(b.name));
combine-iterations (LOW-MEDIUM) — @rules/combine-iterations.md
Combine multiple filter/map into one loop.
const admins = users.filter((u) => u.isAdmin);
const testers = users.filter((u) => u.isTester);
const inactive = users.filter((u) => !u.isActive);
const admins: User[] = [],
testers: User[] = [],
inactive: User[] = [];
for (const user of users) {
if (user.isAdmin) admins.push(user);
if (user.isTester) testers.push(user);
if (!user.isActive) inactive.push(user);
}
cache-property-access (LOW-MEDIUM) — @rules/cache-property-access.md
Cache object properties in loops.
for (let i = 0; i < arr.length; i++) {
process(obj.config.settings.value);
}
const value = obj.config.settings.value;
const len = arr.length;
for (let i = 0; i < len; i++) {
process(value);
}
cache-function-results (MEDIUM) — @rules/cache-function-results.md
Cache expensive function results in module-level Map.
const slugifyCache = new Map<string, string>();
function cachedSlugify(text: string): string {
if (!slugifyCache.has(text)) {
slugifyCache.set(text, slugify(text));
}
return slugifyCache.get(text)!;
}
cache-storage (LOW-MEDIUM) — @rules/cache-storage.md
Cache localStorage/sessionStorage reads in memory.
const storageCache = new Map<string, string | null>();
function getLocalStorage(key: string) {
if (!storageCache.has(key)) {
storageCache.set(key, localStorage.getItem(key));
}
return storageCache.get(key);
}
early-exit (LOW-MEDIUM) — @rules/early-exit.md
Return early when result is determined.
function validate(users: User[]) {
let error = "";
for (const user of users) {
if (!user.email) error = "Email required";
}
return error ? { error } : { valid: true };
}
function validate(users: User[]) {
for (const user of users) {
if (!user.email) return { error: "Email required" };
}
return { valid: true };
}
length-check-first (MEDIUM-HIGH) — @rules/length-check-first.md
Check array length before expensive comparison.
function hasChanges(a: string[], b: string[]) {
return a.sort().join() !== b.sort().join();
}
function hasChanges(a: string[], b: string[]) {
if (a.length !== b.length) return true;
let aSorted = a.toSorted();
let bSorted = b.toSorted();
return aSorted.some((v, i) => v !== bSorted[i]);
}
min-max-loop (LOW) — @rules/min-max-loop.md
Use loop for min/max instead of sort.
const latest = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)[0];
let latest = projects[0];
for (const p of projects) {
if (p.updatedAt > latest.updatedAt) latest = p;
}
hoist-regexp (LOW-MEDIUM) — @rules/hoist-regexp.md
Hoist RegExp creation outside loops.
items.forEach(item => {
if (/pattern/.test(item.text)) { ... }
})
const PATTERN = /pattern/
items.forEach(item => {
if (PATTERN.test(item.text)) { ... }
})
batch-dom-css (MEDIUM) — @rules/batch-dom-css.md
Batch DOM reads before writes to avoid layout thrashing.
element.style.width = "100px";
const width = element.offsetWidth;
element.style.height = "200px";
element.style.width = "100px";
element.style.height = "200px";
const { width, height } = element.getBoundingClientRect();
result-type (MEDIUM) — @rules/result-type.md
Use an explicit Result type for success/failure.
let result = success(data);
if (isFailure(result)) return handleError(result.error);