Today Platform Web — Dev Docs
Toolchain

oxlint + oxfmt

This repo lints with `oxlint` and formats with `oxfmt`. ESLint and Prettier are not installed — and that is intentional.

oxlint and oxfmt are the Rust-rewrite siblings of ESLint and Prettier. The repo uses them exclusively. pnpm lint and pnpm format invoke them at the root.

Why not ESLint / Prettier

Speed, mainly. On this monorepo (~2,000 source files):

ToolCold full-tree run
oxlint~25-30 s
oxfmt --check~700 ms

ESLint with a comparable rule set ran 4-6× slower. Prettier with the same config was ~2 s. The compounding cost in CI and on pre-commit hooks made the switch worth it.

Secondary reasons:

  • Single Rust binary, no Node startup
  • Configuration is simpler; both tools default to sensible behavior
  • The rule overlap with ESLint's eslint-plugin-react / eslint-plugin-jsx-a11y is high enough that the migration was mostly mechanical

oxlint config

oxlint.config.ts declares plugins and ignored paths:

export default defineConfig({
  plugins: ['react', 'jsx-a11y', 'nextjs', 'import'],
  jsPlugins: ['eslint-plugin-storybook', 'eslint-plugin-better-tailwindcss'],

  settings: {
    'better-tailwindcss': {
      entryPoint: 'apps/web/src/app/globals.css',
    },
  },

  ignorePatterns: [
    '**/node_modules/',
    '**/dist/',
    '**/.next/',
    '**/playwright-report/',
    '**/lib/api/generated/',
    // ...
  ],
})

Two notable plugins are JS plugins (jsPlugins:), not native oxlint plugins — eslint-plugin-storybook and eslint-plugin-better-tailwindcss. They run through oxlint's ESLint compatibility shim. This is slower than native rules but lets us keep coverage for Tailwind-class normalization and Storybook's CSF lint without a separate tool.

Severity model

oxlint distinguishes errors (block CI) from warnings (visible but non-blocking). The repo errors-out on:

  • @typescript-eslint/no-unused-vars (with _-prefix escape hatch)
  • import/no-default-export for some paths
  • A handful of accessibility issues

Most React, a11y, and Tailwind rules are warnings. CI's Lint check fails on errors only — Found N warnings and 0 errors exits 0. This keeps the noise floor low while still surfacing improvement targets.

If you see warnings about data-[side=left]:... in apps/admin/sidebar.tsx, that's the better-tailwindcss rule complaining about a non-canonical class shape that was inherited from a shadcn upstream — pre-existing, not your fault.

oxfmt config

oxfmt.config.ts:

export default defineConfig({
  semi: false,
  printWidth: 100,
  singleQuote: true,
  jsxSingleQuote: true,
  trailingComma: 'all',
  tabWidth: 2,
  sortImports: true,
})

Two non-default choices worth knowing:

  • semi: false — no semicolons, anywhere. If you reach for a semicolon, oxfmt will eat it. Code that needs leading-semi protection (top-of-file IIFEs, ASI hazards) should still be flagged in review; we haven't hit one.
  • sortImports: true — imports auto-sort. pnpm format will re-order every import block.

tsconfig*.json files override trailingComma to 'none' because tsc / tsgo treat trailing commas in JSON-with-comments tsconfigs inconsistently.

Commands

pnpm lint                   # oxlint, full tree
pnpm format                 # oxfmt --write, full tree (mutates files)
pnpm format:check           # oxfmt --check, full tree (non-mutating; CI uses this)
pnpm check                  # lint + format:check + typecheck + test

pnpm check is the "what CI runs" alias. Use it before pushing if you want to skip the round trip to GitHub.

What you'll trip on

  • oxfmt doesn't honor .gitignore — it has its own ignorePatterns: in the config. The config ignores .claude/worktrees/ for this reason (sibling worktrees that Claude sessions create would otherwise be reformatted)
  • MDX files in apps/*/content/docs/ are formatted by oxfmt; expect some reflow on first-time saves
  • Generated files under apps/*/src/lib/api/generated/ are ignored by oxlint so the codegen output (pnpm api:generate) doesn't need to match hand-written conventions

On this page