Skip to content

oxlint + Prettier Migration

Migrated the codebase from Biome (all-in-one linter/formatter) to a split setup:

  • oxlint - Fast linter from the oxc project
  • Prettier - Industry-standard code formatter

Biome’s formatter was causing issues with unwanted code transformations (adding semicolons despite configuration, changing indentation). Rather than fight the tool, we switched to a more predictable setup.

Key reasons:

  • oxlint is extremely fast and focused purely on linting
  • Prettier is battle-tested and respects configuration reliably
  • Separating concerns (linting vs formatting) provides better control
  • Both tools are well-maintained with active communities

Alternatives considered:

  • Keep Biome: Configuration issues were causing developer friction
  • ESLint + Prettier: Slower than oxlint, more configuration overhead
Terminal window
# Remove Biome, add oxlint and Prettier
bun remove @biomejs/biome
bun add -d oxlint prettier
# Rename config package
mv packages/biome-config packages/oxlint-config

Created @repo/oxlint-config with layered configurations:

packages/oxlint-config/
├── package.json
├── base.json # Core rules for all packages
├── react.json # React-specific rules (extends base)
└── next.json # Next.js rules (extends react)

Base config (packages/oxlint-config/base.json):

{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"categories": {
"correctness": "error",
"suspicious": "warn",
"perf": "warn"
},
"rules": {
"no-unused-vars": "warn",
"no-console": "off",
"eqeqeq": "error",
"no-var": "error",
"prefer-const": "warn"
},
"env": {
"browser": true,
"node": true,
"es2024": true
},
"overrides": [
{
"files": ["**/*.test.ts", "**/*.test.tsx", "**/tests/**"],
"rules": {
"no-constant-binary-expression": "off"
}
}
]
}

React config (packages/oxlint-config/react.json):

{
"extends": ["./base.json"],
"plugins": ["react", "react-hooks", "jsx-a11y"],
"rules": {
"react/jsx-key": "error",
"react/react-in-jsx-scope": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}

Root config (.prettierrc):

{
"useTabs": true,
"semi": false,
"singleQuote": false,
"trailingComma": "es5",
"printWidth": 100
}

Ignore file (.prettierignore):

node_modules
dist
.next
.astro
.turbo
bun.lock
coverage
**/convex/_generated

Added .vscode/settings.json for consistent editor experience:

{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.insertSpaces": false,
"typescript.tsdk": "node_modules/typescript/lib"
}

Recommended extensions in .vscode/extensions.json:

  • esbenp.prettier-vscode - Prettier formatter
  • oxc.oxc-vscode - oxlint integration
  • bradlc.vscode-tailwindcss - Tailwind IntelliSense
  • astro-build.astro-vscode - Astro support

Each package references the appropriate config via .oxlintrc.json:

Web app (apps/web/.oxlintrc.json):

{
"extends": ["../../packages/oxlint-config/next.json"]
}

UI package (packages/ui/.oxlintrc.json):

{
"extends": ["../oxlint-config/react.json"]
}

Updated lint scripts in each package.json:

{
"scripts": {
"lint": "oxlint ."
}
}
  • oxlint: ^1.42.0 - Fast Rust-based linter
  • prettier: ^3.8.1 - Code formatter

The migration was seamless:

  1. Removed all biome.json files
  2. Added .oxlintrc.json files referencing shared configs
  3. Updated lint scripts from biome check --write to oxlint .
  4. Added format scripts using Prettier

When working with linting and formatting:

  • Linting: Run bun lint (uses oxlint via Turborepo)
  • Formatting: Run bun format (uses Prettier)
  • Config location: packages/oxlint-config/ for lint rules
  • Style: Tabs, no semicolons, double quotes
  • oxlint doesn’t auto-fix - it only reports issues
  • Prettier handles all formatting concerns
  • Single tool (Biome) for linting and formatting
  • Configuration issues causing unwanted code changes
  • Semicolons being added despite asNeeded setting
  • oxlint for fast, reliable linting
  • Prettier for predictable formatting
  • Clear separation of concerns
  • VS Code integration with format-on-save
Terminal window
# Run linting
bun lint
# Check formatting
bun format:check
# Apply formatting
bun format

Expected results:

  • bun lint passes with no errors
  • bun format:check passes after running bun format
  • Code uses tabs and no semicolons