Workbench

Naming and imports

File names that say what's inside, absolute imports, and no barrel files.

File names

The name tells you what kind of file it is before you open it.

KindPatternExample
ComponentPascalCase.tsxProjectCard.tsx
Hookuse-name.tsuse-update-profile-mutation.ts
Schemaname.schema.tslogin.schema.ts
APIname.api.tsauth.api.ts
Query optionsname.query-options.tsprofile.query-options.ts
Storename-store.tsauth-store.ts
Typesname.types.tsprofile.types.ts
ConstantsUPPER_SNAKE_CASE exportQUERY_KEYS

One exception

Feature and shared components use PascalCase. UI primitives in components/ui/ stay kebab-case (button.tsx), like hooks and utilities. Biome enforces the split with a filename rule override; see Biome and Ultracite.

Absolute imports

Use the @/ alias for anything outside the current folder:

import { Button } from '@/components/ui/button';

../../../components/ui/button breaks the moment a file moves, and it's hard to read.

No barrel files

No index.ts files that re-export a folder. Import from the file that defines the thing:

// Good
import { loginSchema } from '@/features/auth/schemas/login.schema';

// Bad
import { loginSchema } from '@/features/auth';

Why:

  • No circular imports. Barrels make it easy for two features to import each other through their index files.
  • Easier to find things. The import path is the file path.
  • Smaller bundles. The bundler doesn't have to pull in a whole folder to get one export.

TypeScript

  • Strict mode, and no any. When a type is truly unknown, use unknown and narrow it.
  • Prefer interface for object shapes, and discriminated unions for values that can be one of several shapes.
  • Let TypeScript infer what it can. Annotate exported function boundaries, not every local variable.
  • Exported utilities get a JSDoc comment that says what they return.

Last updated on

On this page