JavaScript, React & Next.js Style Guide
This guide covers JavaScript, React, and Next.js conventions enforced by Biome and project-specific patterns.
Formatting
Section titled “Formatting”Indentation & Spacing
Section titled “Indentation & Spacing”- 4 spaces for indentation (no tabs)
- Single quotes for JavaScript strings (
'hello') - Double quotes for JSX attributes (
<div className="card">) - Trailing commas on multiline objects and arrays
const items = ['first', 'second', 'third'];Line Length
Section titled “Line Length”- Maximum 120 characters per line
- Break long lines at logical points (after commas, before operators)
TypeScript
Section titled “TypeScript”Type Definitions
Section titled “Type Definitions”- Use
interfacefor object shapes,typefor unions/aliases - Use
typefor primitive aliases and function types - Export types separately when needed
interface Props { title: string; items: string[];}
type Status = 'loading' | 'success' | 'error';- Define component props using
interface Props - Use
extendsto extend other prop types - Use
Omitto exclude properties
interface Props extends React.HTMLAttributes<HTMLDivElement> { title: string; variant?: 'primary' | 'secondary';}React Components
Section titled “React Components”Function Components
Section titled “Function Components”- Use function declarations for top-level exports
- Use arrow functions for inline or nested components
- Always use explicit return types when props are complex
interface Props { title: string;}
export default function MyComponent({ title }: Props) { return <h1>{title}</h1>;}Component Structure
Section titled “Component Structure”- Imports (external, then internal)
- Types/interfaces
- Constants
- Component function
- Return statement
import Image from 'next/image';import type { Media } from '@/payload-types';
interface Props { src: Media;}
const DEFAULT_WIDTH = 800;
export default function MyImage({ src }: Props) { return <Image src={src.url} alt={src.alt} width={DEFAULT_WIDTH} />;}Naming
Section titled “Naming”- Components: PascalCase (
SiteHeader,PrimaryNavigation) - Files: kebab-case (
site-header.tsx,primary-navigation.tsx) - Constants: SCREAMING_SNAKE
const DEFAULT_WIDTH = 800;const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL;Next.js Specifics
Section titled “Next.js Specifics”Server vs Client Components
Section titled “Server vs Client Components”- Default to Server Components
- Add
'use client'only when needed (hooks, browser APIs, event handlers)
'use client';
import { useState } from 'react';
export default function Counter() { const [count, setCount] = useState(0); return <button onClick={() => setCount(c => c + 1)}>{count}</button>;}Images
Section titled “Images”- Always use
next/imagefor images - Use
sizes="auto"for dynamic images - Provide fallback width/height
<Image sizes="auto" src={image.url} alt={image.alt} width={image.width || DEFAULT_WIDTH} height={image.height || DEFAULT_HEIGHT}/>- Use
next/linkfor internal navigation - Combine with
atag or spread props
import Link from 'next/link';
export function MyLink({ href, children }: { href: string; children: React.ReactNode }) { return <Link href={href}>{children}</Link>;}Linting Rules
Section titled “Linting Rules”This project enforces these rules via Biome:
| Rule | Level | Description |
|---|---|---|
useSimplifiedLogicExpression | error | Simplify complex boolean logic |
noNestedComponentDefinitions | error | Don’t define components inside components |
noReactPropAssignments | error | Don’t assign to props directly |
noCommonJs | error | Use ES modules only |
noMagicNumbers | error | No literal numbers in code (use constants) |
useBlockStatements | error | Always use braces for conditionals |
noEnum | error | Use type unions instead of enums |
noParameterAssign | error | Don’t reassign function parameters |
useConsistentArrayType | error | Use string[] not Array<string> |
useReactFunctionComponent | error | Use function components only |
noConsole | warn | Avoid console.log in production |
noAlert | error | Never use alert() |
Common Patterns
Section titled “Common Patterns”Conditional Rendering
Section titled “Conditional Rendering”if (!data) { return null;}
return <div>{data.content}</div>;Destructuring Props
Section titled “Destructuring Props”export default function Component({ title, variant = 'primary', ...rest}: Props) { return <div {...rest}>{title}</div>;}Null Checks
Section titled “Null Checks”if (!isPayloadImage(props.src)) { return null;}
const image = props.src;// image is now guaranteed to existImports
Section titled “Imports”- Node built-ins (path, fs, etc.)
- External packages (next, react, @payload)
- Internal packages (@/, ~/components)
- Relative imports
import path from 'path';import Image from 'next/image';import type { Media } from '@/payload-types';import PrimaryNavigation from '../primary-navigation/primary-navigation';Path Aliases
Section titled “Path Aliases”- Use
@/forsrc/root - Use relative imports for sibling components
import { getSiteHeaderGlobal } from '@/globals/api';import PrimaryNavigation from '../primary-navigation/primary-navigation';Best Practices
Section titled “Best Practices”- Use TypeScript types strictly
- Keep components small and focused
- Extract logic into custom hooks
- Use constants for magic numbers
- Handle null/undefined explicitly
- Use
anytype - Define components inside other components
- Use numeric indexes for array keys
- Leave console.log in production code
- Use CommonJS (
require,module.exports)