Objective: Define standards for modern, maintainable, and performant JavaScript across browser and server environments, covering syntax, modules, async patterns, error handling, security, and tooling.
-
Target ES2022+ features for new code. If cross-browser or legacy Node.js support is required, configure a transpiler (esbuild, SWC, or Babel) and document the target in the project's build config.
-
Always use
constby default. Useletonly when the variable must be reassigned. Never usevar— it has function scope and is prone to hoisting bugs. -
Use arrow functions (
() => {}) for callbacks, short functions, and method references in closures. Use namedfunctiondeclarations for top-level, reusable functions that benefit from hoisting and named stack traces. -
Use template literals for string interpolation:
`Hello ${name}`. Prefer multi-line template literals over string concatenation. -
Use optional chaining (
?.) and nullish coalescing (??). Avoid||for defaults when0,"", orfalseare valid values:// ❌ Incorrect: falsy defaults clobber valid values const timeout = config.timeout || 5000; // wrong if timeout=0 is valid // ✅ Nullish: only uses default when null/undefined const timeout = config.timeout ?? 5000; const url = config?.api?.baseUrl ?? "https://api.example.com";
-
Use object and array destructuring for cleaner code:
const { id, name, email = "unknown" } = user; const [first, second, ...rest] = items; const { status, data: { results, total }, } = response;
-
Use spread operator for shallow cloning and merging:
const updated = { ...original, name: "New Name" }; const merged = [...arrayA, ...arrayB];
-
Use
structuredClone()for deep cloning — it handles Date, RegExp, Map, Set, TypedArray correctly:const clone = structuredClone(complexObject); // ✅ Native, handles all types // ❌ JSON.parse/stringify loses Dates, undefined, functions, Maps, Sets
-
Use private class fields (
#field) and static class blocks for modern, encapsulated class design.
-
Use ES Modules (
import/export) exclusively in new code. Do NOT mix with CommonJS (require/module.exports) in the same module. Specify"type": "module"inpackage.jsonfor Node.js ESM. -
Organize imports in this order, separated by blank lines:
- Node.js built-ins (
node:fs,node:path) - External libraries (
@scope/library,express) - Internal modules (
./services/user,../utils) - Type-only imports (if not using TypeScript, document types via JSDoc)
- Node.js built-ins (
-
Use explicit named exports over default exports for better refactoring, IDE autocompletion, and tree-shaking:
// ✅ Named export — IDE can find all usages, rename safely export function createUser(data) { ... } export const MAX_RETRIES = 3; // Default export is fine for framework entry points (page components, route handlers)
-
Avoid circular dependencies. Use a linting rule (
import/no-cycle) to detect them. Circular dependencies cause module initialization issues and are hard to debug. -
Prefer specific named imports over namespace imports to enable tree-shaking:
// ✅ Only imports what's needed import { pick, omit } from "lodash-es"; // ❌ Imports entire module — defeats tree-shaking import _ from "lodash";
-
Use
async/awaitfor all asynchronous operations. Avoid raw.then()/.catch()chains for complex logic — they produce deeply nested, hard-to-follow code:// ✅ async/await — clear sequential flow async function loadUserProfile(userId) { const user = await userService.findById(userId); const permissions = await permissionService.forUser(user.id); return { ...user, permissions }; }
-
Wrap
awaitcalls intry...catchblocks for explicit error handling:async function submitOrder(cart) { try { const order = await orderService.create(cart); await notificationService.sendConfirmation(order.id); return order; } catch (error) { logger.error("Order submission failed", { cartId: cart.id, error }); throw new ServiceError("Order submission failed", { cause: error }); } }
-
Use
Promise.all()for concurrent independent operations; usePromise.allSettled()when you need all results regardless of failures:// Parallel — fails fast if any rejects const [user, posts, settings] = await Promise.all([ fetchUser(id), fetchPosts(id), fetchSettings(id) ]); // Parallel — collects all results const results = await Promise.allSettled(requests); const { fulfilled, rejected } = results.reduce(...);
-
Set timeouts on all external async operations using
AbortController:const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); try { const response = await fetch(url, { signal: controller.signal }); const data = await response.json(); return data; } finally { clearTimeout(timeoutId); }
-
Handle unhandled Promise rejections explicitly. Register a global handler in Node.js:
process.on("unhandledRejection", (reason, promise) => { logger.error("Unhandled Promise rejection", { reason, promise }); process.exit(1); // crash and let orchestrator restart });
-
Offload CPU-intensive synchronous work to Web Workers (browser) or
worker_threads(Node.js) to avoid blocking the event loop:// Web Worker for CPU-bound image processing const worker = new Worker("./image-processor.worker.js"); worker.postMessage({ imageData });
-
Never use empty
catchblocks that silently swallow errors:// ❌ Silent failure — impossible to debug try { something(); } catch (e) {} // ✅ At minimum, log with full context try { something(); } catch (e) { logger.error("operation failed", { context, error: e }); throw e; // or handle appropriately }
-
Always throw
Errorobjects (or subclasses), never plain strings or objects:// ❌ Plain string — no stack trace, no instanceof checks throw "Something went wrong"; // ✅ Error object — has stack trace, can be caught by type throw new Error("Something went wrong");
-
Create custom error classes for domain-specific errors:
class ValidationError extends Error { constructor(message, { field, value } = {}) { super(message); this.name = "ValidationError"; this.field = field; this.value = value; } } class NotFoundError extends Error { constructor(resource, id) { super(`${resource} with id ${id} not found`); this.name = "NotFoundError"; this.resource = resource; this.id = id; } }
-
Use the
causeoption to chain errors (ES2022+):throw new ServiceError("User registration failed", { cause: dbError });
-
Never pass user input to
innerHTML,eval(),document.write(), orsetTimeout(string)— these are XSS injection points:// ❌ XSS vulnerability element.innerHTML = user.name; // ✅ Safe text content element.textContent = user.name; // Or use DOM methods: const el = document.createElement("span"); el.textContent = user.name; container.appendChild(el);
-
Use Trusted Types policies (browser) to prevent DOM XSS injection at scale. Configure a
defaultpolicy that enforces safe HTML generation. -
Set Content Security Policy headers to restrict resource loading and prevent injection attacks.
-
Sanitize HTML before rendering (when client-controlled rich text is unavoidable): use
DOMPurify.sanitize(html, { USE_PROFILES: { html: true } }).
- Avoid mutating function arguments or shared global state — write pure functions where possible.
- Limit function length to < 40 lines. If a function does more than one thing, extract it into smaller, focused functions.
- Avoid magic numbers — use named constants:
const MAX_LOGIN_ATTEMPTS = 5;instead ofif (count > 5).
-
Lint with ESLint using
@eslint/js+ environment-appropriate plugins. Enforce in CI witheslint --max-warnings 0:# Recommended rules to enable: no-console: "warn" no-debugger: "error" no-unused-vars: "error" no-var: "error" prefer-const: "error" eqeqeq: "error" no-eval: "error" no-implied-eval: "error"
-
Format with Prettier. Commit
.prettierrc. Enforce in CI:prettier --check .. -
Use JSDoc for public function documentation in non-TypeScript projects:
/** * Creates a new user account. * @param {CreateUserParams} params - User creation parameters * @returns {Promise<User>} The created user * @throws {ValidationError} If email is already taken */ async function createUser(params) { ... }
-
Profile performance-critical paths with browser DevTools Performance tab or Node.js
--profbefore optimizing. Never prematurely optimize without profiling data.