frontend-js-best-practices — Claude Code skill: how to install
Provides 17 performance and style rules for JavaScript and TypeScript. Helps agents optimize loops, reduce algorithmic complexity, and avoid array mutations.
npx skills add sergiodxa/agent-skills --skill frontend-js-best-practices -a claude-codeWhat frontend-js-best-practices does
This skill guides your coding agent through performance optimizations in JavaScript and TypeScript. It replaces O(n) array searches with O(1) Set and Map lookups, switches mutating sort() calls to toSorted(), and combines chained filter() and map() passes into single loops.
The rules also address typing and code structure. The skill discourages type assertions like as Type, preferring runtime type guards or Zod schemas. For named routines, it enforces standard function declarations over arrow syntax, while disallowing default exports outside Remix route files.
The guidelines strictly mandate let over const inside all function scopes. This convention will immediately clash with standard ESLint configurations that enforce prefer-const.
What it helps with
- optimize repeated array lookups using Map and Set
- eliminate array mutations by adopting toSorted()
- replace unsafe as Type assertions with Zod validation
- merge chained filter and map operations into one loop
- cache expensive function outputs and localStorage reads
Example prompts for the agent
- Review this data aggregation module and optimize loop performance.
- Refactor our API response handler to use Zod schemas instead of as casts.
- Combine these separate array filters into a single iteration pass.
How to install frontend-js-best-practices
1Skills CLI (Vercel)
npx skills add sergiodxa/agent-skills --skill frontend-js-best-practices -a claude-code2Manually: put the files in place
~/.claude/skills/frontend-js-best-practices/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/frontend-js-best-practices/
1Skills CLI (Vercel)
npx skills add sergiodxa/agent-skills --skill frontend-js-best-practices -a codex2Manually: put the files in place
.agents/skills/frontend-js-best-practices/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add sergiodxa/agent-skills --skill frontend-js-best-practices -a cursor2Manually: put the files in place
.cursor/skills/frontend-js-best-practices/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install sergiodxa/agent-skills frontend-js-best-practices2Manually: put the files in place
.github/skills/frontend-js-best-practices/SKILL.md1Skills CLI (Vercel)
npx skills add sergiodxa/agent-skills --skill frontend-js-best-practices -a gemini-cli2Platform CLI
gemini skills install https://github.com/sergiodxa/agent-skills --path skills/frontend-js-best-practicesManually: put the files in place
.devin/skills/frontend-js-best-practices/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add sergiodxa/agent-skills --skill frontend-js-best-practices -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about frontend-js-best-practices
How do I install frontend-js-best-practices in Claude Code?
Run npx skills add sergiodxa/agent-skills --skill frontend-js-best-practices -a claude-code in your terminal.
What does frontend-js-best-practices do?
It instructs your coding assistant to apply 17 specific rules for JavaScript and TypeScript, focusing on algorithmic efficiency, caching, and clean type guards.
How does this skill handle variable declarations?
It requires const for module-level primitives and enforces let for all variables declared inside functions.
Source text
SKILL.md · MIT
---
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.
```typescript
// Module level: const with UPPER_SNAKE_CASE for primitives
const MAX_RETRIES = 3;
const userCache = new Map<string, User>();
// Inside functions: always let
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.
```typescript
// Good: function declaration
function calculateTotal(items: Item[]): number {
let total = 0;
for (let item of items) {
total += item.price;
}
return total;
}
// Good: arrow for inline callbacks
let active = users.filter((u) => u.isActive);
// Good: arrow when type requires it
const handler: ActionFunction = async ({ request }) => {
// ...
};
```
### no-default-exports (MEDIUM) — @rules/no-default-exports.md
Use named exports. Avoid default exports (except Remix route components).
```typescript
// Bad: default export
export default function formatCurrency(amount: number) { ... }
// Good: named export
export function formatCurrency(amount: number) { ... }
// Exception: Remix routes use default export named "Component"
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.
```typescript
// Bad: type assertion
let user = response.data as User;
// Good: Zod validation
let user = UserSchema.parse(response.data);
// Good: type guard
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.
```typescript
// Bad: restates the code
// Set the user's name
let userName = user.name;
// Good: explains business rule
// Transactions under $250 don't require written acknowledgment per policy
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.
```typescript
// Bad: O(n) per check
const allowedIds = ["a", "b", "c"];
items.filter((item) => allowedIds.includes(item.id));
// Good: O(1) per check
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.
```typescript
// Bad: O(n) per lookup = O(n*m) total
orders.map((order) => ({
...order,
user: users.find((u) => u.id === order.userId),
}));
// Good: O(1) per lookup = O(n+m) total
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.
```typescript
// Bad: mutates original array
const sorted = users.sort((a, b) => a.name.localeCompare(b.name));
// Good: creates new sorted array
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.
```typescript
// Bad: 3 iterations
const admins = users.filter((u) => u.isAdmin);
const testers = users.filter((u) => u.isTester);
const inactive = users.filter((u) => !u.isActive);
// Good: 1 iteration
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.
```typescript
// Bad: repeated lookups
for (let i = 0; i < arr.length; i++) {
process(obj.config.settings.value);
}
// Good: cached lookup
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.
```typescript
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.
```typescript
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.
```typescript
// Bad: continues after finding error
function validate(users: User[]) {
let error = "";
for (const user of users) {
if (!user.email) error = "Email required";
}
return error ? { error } : { valid: true };
}
// Good: returns immediately
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.
```typescript
// Bad: always sorts even when lengths differ
function hasChanges(a: string[], b: string[]) {
return a.sort().join() !== b.sort().join();
}
// Good: early return if lengths differ
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.
```typescript
// Bad: O(n log n)
const latest = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)[0];
// Good: O(n)
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.
```typescript
// Bad: creates regex every iteration
items.forEach(item => {
if (/pattern/.test(item.text)) { ... }
})
// Good: create once
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.
```typescript
// Bad: interleaved reads/writes force reflows
element.style.width = "100px";
const width = element.offsetWidth; // forces reflow
element.style.height = "200px";
// Good: batch writes, then read
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.
```typescript
let result = success(data);
if (isFailure(result)) return handleError(result.error);
```
© sergiodxa · MIT · sergiodxa/agent-skills