Code Conventions
This guide establishes coding standards and conventions for maintaining a consistent, readable, and maintainable codebase.
File and Directory Naming
Section titled “File and Directory Naming”General Rules
Section titled “General Rules”- Use kebab-case for all file and directory names
- Use descriptive names that clearly indicate purpose
- Avoid abbreviations unless they’re widely understood
Component Files
Section titled “Component Files”src/components/├── newsletter-form/│ ├── index.ts # Barrel export│ ├── newsletter-form.tsx # Main component│ ├── newsletter-form.stories.tsx # Storybook stories│ └── types.ts # Component-specific typesPage Files
Section titled “Page Files”src/pages/├── articles/│ ├── [slug]/│ │ ├── index.astro # Main page component│ │ ├── _components/ # Page-specific components│ │ └── _graphql/ # Page-specific data fetching│ └── index.astro # Article listing pageUtility and Helper Files
Section titled “Utility and Helper Files”src/utils/├── format-date/│ ├── index.ts # Main utility function│ ├── format-date.spec.ts # Tests│ └── types.ts # Type definitionsComponent Conventions
Section titled “Component Conventions”Component Structure
Section titled “Component Structure”Astro Components (.astro):
---// 1. Type importsimport type { ComponentProps } from "astro/types";
// 2. Component importsimport { BaseComponent } from "#components/base";
// 3. Utility importsimport { formatDate } from "#utils/format-date";
// 4. Interface definitioninterface Props { title: string; date?: Date; className?: string;}
// 5. Props destructuring with defaultsconst { title, date = new Date(), className = "", ...rest } = Astro.props;
// 6. Data processingconst formattedDate = formatDate(date);---
<!-- 7. Template with semantic HTML --><article class:list={["article", className]} {...rest}> <h1 class="article__title">{title}</h1> <time class="article__date">{formattedDate}</time> <slot /></article>SolidJS Components (.tsx):
// 1. Framework importsimport { Component, createSignal, createMemo } from 'solid-js';
// 2. Type importsimport type { JSX } from 'solid-js';
// 3. Utility importsimport { cn } from '#utils/classnames';
// 4. Interface definitioninterface Props { initialValue?: string; onSubmit: (value: string) => void; className?: string;}
// 5. Component definitionexport const InteractiveForm: Component<Props> = (props) => { // 6. Signals and state const [value, setValue] = createSignal(props.initialValue ?? ''); const [loading, setLoading] = createSignal(false);
// 7. Computed values const isValid = createMemo(() => value().length > 0);
// 8. Event handlers const handleSubmit = async (e: Event) => { e.preventDefault(); if (!isValid()) return;
setLoading(true); try { await props.onSubmit(value()); } catch (error) { console.error('Form submission failed:', error); } finally { setLoading(false); } };
// 9. JSX return return ( <form onSubmit={handleSubmit} class={cn('form', props.className)} > <input type="text" value={value()} onInput={(e) => setValue(e.currentTarget.value)} disabled={loading()} class="form__input" /> <button type="submit" disabled={!isValid() || loading()} class="form__submit" > {loading() ? 'Submitting...' : 'Submit'} </button> </form> );};Naming Conventions
Section titled “Naming Conventions”Variables and Functions
Section titled “Variables and Functions”// Use camelCase for variables and functionsconst userName = "john_doe";const isAuthenticated = true;const getUserProfile = async (id: string) => { /* ... */};
// Use descriptive namesconst fetchUserArticles = async (userId: string) => { /* ... */}; // ✅const getData = async (id: string) => { /* ... */}; // ❌
// Boolean variables should be questionsconst isLoading = true; // ✅const hasErrors = false; // ✅const loading = true; // ❌ (not descriptive enough)Constants
Section titled “Constants”// Use SCREAMING_SNAKE_CASE for constantsconst API_BASE_URL = "https://api.example.com";const MAX_RETRY_ATTEMPTS = 3;const DEFAULT_TIMEOUT = 5000;
// Group related constantsconst VALIDATION_RULES = { MIN_PASSWORD_LENGTH: 8, MAX_USERNAME_LENGTH: 50, ALLOWED_EMAIL_DOMAINS: ["company.com", "contractor.com"],} as const;Types and Interfaces
Section titled “Types and Interfaces”// Use PascalCase for types and interfacesinterface UserProfile { id: string; name: string; email: string;}
type ArticleStatus = "draft" | "published" | "archived";
// Prefix interfaces with 'I' only when needed for disambiguationinterface Props { title: string;}
interface IUserService { // Only when distinguishing from UserService class getUser(id: string): Promise<User>;}CSS Classes
Section titled “CSS Classes”/* Use BEM methodology for component-specific styles */.article { /* Block */}
.article__title { /* Element */}
.article__title--large { /* Modifier */}
/* Use Tailwind utilities for most styling */<div class="bg-primary text-secondary p-4 rounded-lg">Import Organization
Section titled “Import Organization”Import Order
Section titled “Import Order”// 1. Node modulesimport { Component } from "solid-js";import type { JSX } from "solid-js";
// 2. Internal modules (with path aliases)import { Button } from "#components/button";import { formatDate } from "#utils/format-date";import type { User } from "#types/user";
// 3. Relative importsimport "./component.css";import { localHelper } from "./utils";Path Aliases
Section titled “Path Aliases”// Use configured path aliasesimport { Button } from "#components/button"; // ✅import { formatDate } from "#utils/format-date"; // ✅import { Button } from "../../components/button"; // ❌Error Handling
Section titled “Error Handling”Client-Side Error Handling
Section titled “Client-Side Error Handling”// Use structured error handling with Airbraketry { const result = await apiCall(); return result;} catch (error) { // Log for debugging console.error("API call failed:", { error: error.message, component: "ComponentName", context: { userId: props.userId }, });
// Report to monitoring const { airbrakeClient } = await import("#utils/airbrake/browser"); airbrakeClient.notify({ error, context: { component: "ComponentName", userId: props.userId, }, });
// Return user-friendly fallback return { error: "Unable to load data. Please try again." };}Server-Side Error Handling
Section titled “Server-Side Error Handling”// Return appropriate HTTP status codesexport async function GET({ params, request }) { try { const data = await fetchData(params.id);
if (!data) { return new Response(JSON.stringify({ error: "Not found" }), { status: 404, headers: { "Content-Type": "application/json" }, }); }
return new Response(JSON.stringify(data), { headers: { "Content-Type": "application/json" }, }); } catch (error) { console.error("Server error:", error); return new Response(JSON.stringify({ error: "Internal server error" }), { status: 500, headers: { "Content-Type": "application/json" }, }); }}Documentation Standards
Section titled “Documentation Standards”Component Documentation
Section titled “Component Documentation”/** * Interactive form component for user input with validation * * @example * ```tsx * <InteractiveForm * initialValue="Hello" * onSubmit={(value) => console.log(value)} * className="my-form" * /> * ``` */interface Props { /** Initial value for the form input */ initialValue?: string; /** Callback function called when form is submitted */ onSubmit: (value: string) => void; /** Additional CSS classes to apply */ className?: string;}Function Documentation
Section titled “Function Documentation”/** * Formats a date according to the application's locale settings * * @param date - The date to format * @param options - Formatting options * @returns Formatted date string * * @example * ```typescript * const formatted = formatDate(new Date(), { includeTime: true }); * // Returns: "March 15, 2024 at 2:30 PM" * ``` */export function formatDate(date: Date, options: FormatOptions = {}): string { // Implementation}Testing Conventions
Section titled “Testing Conventions”Test File Organization
Section titled “Test File Organization”import { render, screen } from '@testing-library/solid';import { Component } from './Component';
describe('Component', () => { it('should render with default props', () => { render(() => <Component title="Test" />); expect(screen.getByText('Test')).toBeInTheDocument(); });
it('should handle user interaction', async () => { const handleClick = vi.fn(); render(() => <Component title="Test" onClick={handleClick} />);
const button = screen.getByRole('button'); await user.click(button);
expect(handleClick).toHaveBeenCalledOnce(); });});Test Naming
Section titled “Test Naming”- Use descriptive test names that explain the expected behavior
- Group related tests using
describeblocks - Use “should” statements for test descriptions
Performance Guidelines
Section titled “Performance Guidelines”Component Optimization
Section titled “Component Optimization”// Use createMemo for expensive computationsconst expensiveValue = createMemo(() => { return heavyComputation(props.data);});
// Avoid creating objects in JSXconst styles = { color: 'red' }; // ✅ Define outside renderreturn <div style={styles}>Content</div>;
// Not this:return <div style={{ color: 'red' }}>Content</div>; // ❌ Creates new object each renderBundle Size Optimization
Section titled “Bundle Size Optimization”// Use dynamic imports for large dependenciesconst heavyLibrary = await import("heavy-library");
// Tree shake unused exportsimport { specificFunction } from "large-library"; // ✅import * as library from "large-library"; // ❌ Imports everythingAccessibility Guidelines
Section titled “Accessibility Guidelines”Semantic HTML
Section titled “Semantic HTML”<!-- Use proper semantic elements --><main> <article> <header> <h1>Article Title</h1> <time datetime="2024-03-15">March 15, 2024</time> </header> <p>Article content...</p> </article></main>ARIA Attributes
Section titled “ARIA Attributes”// Provide proper labels and descriptions<button aria-label="Close dialog" aria-describedby="close-help" onClick={handleClose}> ×</button><div id="close-help" class="sr-only"> Closes the current dialog and returns to the main content</div>Linting and Formatting
Section titled “Linting and Formatting”ESLint Rules
Section titled “ESLint Rules”- Follow the configured ESLint rules in
.eslintrc.js - Use
npm run lintto check for violations - Fix auto-fixable issues with
npm run lint:fix
Prettier Configuration
Section titled “Prettier Configuration”- Code formatting is handled automatically by Prettier
- Configuration is in
.prettierrc - VS Code should format on save if configured properly
Git Conventions
Section titled “Git Conventions”Commit Messages
Section titled “Commit Messages”# Format: type(scope): descriptionfeat(components): add newsletter signup componentfix(api): handle null responses in user endpointdocs(readme): update installation instructionsrefactor(utils): simplify date formatting logicBranch Naming
Section titled “Branch Naming”# Format: {initials}-{ticket-number|description}jd-lpweb-1234-add-user-profilesm-fix-navigation-bugak-update-design-tokens