- Do not allow truthy/falsy checks (e.g., if (value)). All checks must be explicit (e.g., if (value !== null), if (value === true), if (value.length > 0)).
- If a variable represents a boolean value, its name should be prefixed with
isorhas(e.g.,isActive,hasChildren).
- Strictly prefer
interfacefor defining object shapes (data models, props, state) due to better performance and clearer error messages. - Use
typeonly for unions, primitives, or utility types where interfaces cannot be used.
- Strictly prohibit TypeScript features that emit runtime code or require compilation/transformation.
- Prohibited Features: Enums, namespace merging, decorators, and constructor parameter properties (shorthand class props).
- Reasoning: Code must be compatible with Node.js
--experimental-strip-typesand standard JavaScript runtimes. TypeScript should be treated as type-strippable syntax only.
- Prefer
nulloverundefinedfor optional values or missing data. - Reasoning:
nullis valid JSON and standard in databases, reducing the need for conversion at API boundaries.
- Prefer async/await for all asynchronous code for readability, maintainability, and easier error handling. Avoid raw Promises or .then/.catch chaining to keep code consistent and debuggable.
- No strict convention for error messages. If surfacing messages to users (e.g., via toast), avoid technical language and use user-friendly messages.
- For developer-facing errors, include enough context for debugging. If possible, include a code or type property for programmatic handling.
- Vue Single-File Components (
.vue) must use PascalCase (e.g.,UserCard.vue). - General files (e.g., utils, hooks) must use camelCase (e.g.,
useFetch.ts). - Exception: Follow framework-specific conventions (e.g., Nuxt pages/layouts) where strict naming is required by the framework.
- Organize code using feature folders for scalability and easier onboarding. Each feature should have its own folder with a structure similar to the app-wide organization. The following is just an example structure—adapt as needed for your project:
lib/
api/
db/
components/
utils/
features/
featureA/
lib/
api/
db/
components/
utils/
- Prefer zod for validation, es-toolkit for utilities, the Vite ecosystem (rolldown, oxfmt, oxlint) for build and tooling, date-fns for date handling, and Vitest as the preferred testing library.
- Avoid introducing libraries that overlap with these unless there is a clear benefit.
- Accessibility (a11y) is generally not a priority unless otherwise specified. However, avoid introducing barriers for users, and consider a11y for public-facing features.
- STRICT: All commit messages must follow the Conventional Commits format:
<type>(<scope>): <subject>.- Standard types:
feat,fix,docs,style,refactor,test,chore.
- Standard types:
- No strict requirements for code review, PR descriptions, or documentation format.
- Clear PR descriptions are encouraged for future maintainability.
- Prefer immutability in shared or global state for predictability and safety. Mutation is acceptable in local, isolated contexts for performance or simplicity.
- No strict rules for handling environment variables or secrets. However, never hardcode secrets in the codebase and consider using environment variable management tools.
- Deprecated code can generally be removed immediately unless there is a specific reason to keep it. Document breaking changes in a CHANGELOG or similar if relevant.
- Prefer dependency injection (DI) where it makes sense, especially in backend code, to improve testability and modularity.
- Use factory functions and object parameters for DI instead of classes, to keep code self-documenting and flexible.
Example:
function createLogger() {
return {
log({ message }: { message: string }) {
console.log(message);
},
};
}
function createUserService({
logger,
}: {
logger: { log: (payload: { message: string }) => void };
}) {
return {
createUser(name: string) {
logger.log({ message: `User created: ${name}` });
// ...other logic
},
};
}
const userService = createUserService({ logger: createLogger() });
userService.createUser("Alice");- Prefer global (absolute) imports for most modules for clarity and easier refactoring. Exceptions can be made for files that logically belong together (e.g., sibling components).
- Group and order imports for readability (e.g., external libraries, internal modules, styles).
- Strictly disable/avoid Nuxt auto-imports.
- All imports (Vue primitives like
ref,computed, and Nuxt composables) must be explicitly imported at the top of the file. - Reasoning: To ensure code clarity, better IDE support, and easier refactoring.
- Never use default exports. Always use named exports for clarity, discoverability, and safer refactoring.
- Use optional chaining and nullish coalescing only when needed, guided by TypeScript. Avoid overusing them to keep code explicit.
- Always use
===for comparisons, except when explicitly checking for null or undefined, where==is allowed (unless a distinction is needed).
- Prefer destructuring for function parameters and variables to improve clarity and reduce repetition. Avoid repetitive property access like
params.x,params.y. - Prefer the revealing module pattern for code with state instead of classes, to keep state encapsulated and avoid using
this.x,this.y, etc.
- Prefer the Composition API for Vue components for scalability and code reuse.
- Explicitly prefer
definePropsanddefineModelusing runtime-free type-only syntax (generics) to stay as close to pure TypeScript as possible. - Example:
defineProps<{ id: string }>()instead ofdefineProps({ id: String }).
- Pinia is the preferred state management library for its modern API and type safety.
- Only catch errors when there is a way to gracefully resolve the problem. Let unhandled errors propagate to global handlers/loggers. Avoid unnecessary try/catch blocks.
- Tailwind CSS is preferred for styling. Use utility classes to enforce consistency, but compose them for readability in complex components.
- Magic numbers and strings should generally be defined as constants to improve maintainability and reduce errors from typos.
- Using
as constis encouraged to ensure literal types and immutability where appropriate. - Using
satisfiesis encouraged to validate that objects conform to specific types without changing their inferred type.
Examples:
// Good: as const for literal types
const directions = ["up", "down", "left", "right"] as const;
// Good: satisfies for type validation
const config = {
port: 8080,
debug: true,
} satisfies AppConfig;- Using
anyis strictly prohibited. Always use specific, safe types to maximize TypeScript's safety guarantees. - Using
ascasting is discouraged. Prefer proper type inference or safe type guards instead.
Example:
// Not allowed
let value: any;
// Discouraged
const foo = bar as string;
// Preferred
function handle(input: string | number) {
if (typeof input === "string") {
// ...
}
}- In Vue single-file components (
.vue), always order sections as follows for consistency and easier navigation:<script><template><style>
Example:
<script setup lang="ts">
// ...
</script>
<template>
<!-- ... -->
</template>
<style>
/* ... */
</style>- Avoid passing function references directly into other functions (e.g.,
array.map(doStuff)). - Instead, use an inline arrow function to ensure type safety and accurate parameter mapping. This helps TypeScript catch errors if the function signature changes, even if callback signatures overlap.
Example:
// Not preferred
array.map(doStuff);
// Preferred
array.map((x) => doStuff({ name: x }));- When using regular expressions, always provide comments with example matching strings. This helps clarify the intent and usage of the regex for future readers and maintainers.
Example:
// Matches: "user@example.com", "test123@domain.org"
const emailRegex = /[\w.-]+@[\w.-]+\.[a-zA-Z]{2,}/;- Code should be self-documenting. Avoid comments that explain what the code is doing; instead, write clear code. Comments should generally explain why something is done, not what is happening.
- Exceptions can be made for logic-heavy or complex code, where brief explanations of what is happening are acceptable.
- Prefer functions that take a single object as a parameter. This makes function calls self-documenting and easier to maintain, and makes it easier to extend parameters in the future.
- This rule also applies to callback parameters (e.g., event handlers, array methods), and to function payloads (e.g., logging, actions).
Example:
// Preferred
function createUser({ name, age }: { name: string; age: number }) {}
function log({ message }: { message: string }) {}
// Not preferred
function createUser(name: string, age: number) {}
function log(message: string) {}- Prefer using the
functionkeyword for function declarations for hoisting and readability. - Use arrow functions (
const fn = () => {}) only for quick, inline callbacks (e.g., witharray.map, event handlers, etc.).
Example:
// Preferred for declarations
function increment() {
// ...
}
// Preferred for callbacks
numbers.map((n) => n + 1);Always use Array<T> instead of T[] for array types.
This is more explicit and consistent, especially in generic contexts and when working with utility types.
Example:
// Preferred
const numbers: Array<number> = [1, 2, 3];
// Not preferred
const numbers: number[] = [1, 2, 3];