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):
| Tool | Cold 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-a11yis 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-exportfor 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
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 formatwill 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 + testpnpm 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
oxfmtdoesn't honor.gitignore— it has its ownignorePatterns: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
tsconfig project references
Three tsconfig files per project — root / app / node — extending a single shared base. Why each one exists and what goes in it.
Next.js 16
Three Next.js apps on Next 16 with Turbopack and the renamed `proxy.ts` middleware file. What changed from Next 15 and what to watch for.