Privacy Policy© 2026 DEV BAK - TECH BLOG. All rights reserved.
DEV BAK - TECH BLOG
frontend

When Linting Gets 62x Faster, Your Development Habits Change — Migrating from ESLint to Oxlint in a Vite Project

Have you ever felt that your linter is slow? I did too. I used to think waiting nearly 30 seconds every time I ran pnpm lint on a mid-sized React project was just normal — until I switched to Oxlint and saw 499ms. I actually suspected at first that something was misconfigured. The official benchmark numbers are 31 seconds vs. 499ms, roughly 62x faster, and when you experience it firsthand, you realize just how much that number affects your coding flow.

Linting is the practice of automatically detecting potential bugs and style issues as you write code. ESLint has long been the standard in the JavaScript/TypeScript ecosystem, but lately Oxlint — written in Rust — has been rapidly challenging that position. Beyond raw speed, reduced CI linting costs and lower configuration maintenance overhead are reasons you feel in practice.

This article walks you through migrating an existing Vite + ESLint project to Oxlint, step by step — from running both in parallel, to a complete replacement, to a preview of the Vite+ integrated toolchain. After reading this, you'll be able to choose the right migration strategy for your own project.


Core Concepts

Why Is Oxlint So Fast

At first I only knew "Rust-written things are fast," but after seeing the actual difference, I got curious about the reason. ESLint is fundamentally designed around a single-threaded model, so it slows down linearly as more rules are added. Oxlint, on the other hand, is written in Rust as part of the OXC (Oxidation Compiler) project, and it processes files in parallel — so the more files you have, the greater the benefit.

OXC (Oxidation Compiler) — An open-source project reimplementing JavaScript/TypeScript tooling in Rust. The parser, linter (Oxlint), bundler (Rolldown), and formatter (Oxfmt) all share the same core engine.

If the speed difference doesn't quite click, these numbers will help. On the official benchmark with an M2 Max, a task that takes ESLint 31 seconds takes Oxlint 499ms. As of 2026, 695+ rules cover a substantial portion of the core rules from major plugins like eslint-plugin-react and typescript-eslint, making it realistically possible in many projects to gain speed without losing rule coverage.

Oxlint's Place in the Vite Ecosystem

In early 2026, VoidZero — the company behind Vite — launched Vite+ (the vp CLI) as a public alpha. It's an integrated toolchain that bundles Vite, Vitest, Oxlint, Oxfmt, Rolldown, and tsdown into a single binary. Cloudflare's acquisition of VoidZero is also accelerating corporate investment in this ecosystem.

Tool Role Status
Oxlint Linter (ESLint replacement) v1.0 Stable
Rolldown Bundler (default in Vite 8+) Stable
Oxfmt Formatter (Prettier replacement) In development
Vite+ Integrated CLI Alpha

If you want to adopt it right now, you can attach it to an existing Vite project via vite-plugin-oxlint. If you want to go all in, a full migration to Vite+ is also an option. This article covers both paths.

The Difference Between @oxlint/migrate and eslint-plugin-oxlint

I was confused by these two packages when I first prepared the migration, but they serve completely different purposes.

Package Role When to Use
@oxlint/migrate CLI that auto-generates .oxlintrc.json from ESLint config When completely removing ESLint
eslint-plugin-oxlint Disables rules in ESLint that Oxlint already covers When running both linters in parallel

Key Difference — @oxlint/migrate converts your ESLint config into an Oxlint config, while eslint-plugin-oxlint is a bridge for running both linters simultaneously to prevent duplicate checks. You don't need to install both.


Practical Application

Example 1: Integrating with vite-plugin-oxlint in 5 Minutes

This is the fastest approach. You don't have to remove ESLint right away — you can start by layering Oxlint on top of your existing build pipeline. I tried this method for my initial adoption and was surprised by how low the barrier was; it caught errors immediately even without a config file.

bash
pnpm add -D vite-plugin-oxlint
typescript
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import oxlintPlugin from 'vite-plugin-oxlint'
 
export default defineConfig({
  plugins: [
    react(),
    oxlintPlugin({
      deny: ['correctness', 'perf'],  // Fail build on violations in these categories
      warn: ['suspicious'],            // Pass build but print warnings
      allow: ['debugger'],             // Disable by rule name
    }),
  ],
})

The allow option accepts both rule names ('debugger') and categories ('style'). Valid categories are correctness, perf, suspicious, style, and pedantic.

Option Description
deny Treat violations of rules in these categories as build failures
warn Pass the build but print warnings to the terminal
allow Disable specific rules (by name) or categories

Running pnpm dev or pnpm build now includes linting. Because it catches errors immediately using defaults even without .oxlintrc.json, starting without a config file and adding one only when needed is a perfectly practical approach.

Example 2: Auto-Converting ESLint Config with @oxlint/migrate

If you're using ESLint flat config (v9 style), automatic conversion is available. If you're on the v8 legacy config (.eslintrc.*), you'll need to convert to flat config first — @oxlint/migrate requires this order to read your config correctly.

bash
# If on ESLint v8 legacy config, convert to flat config first
npx @eslint/migrate-config .eslintrc.js
 
# Auto-generate .oxlintrc.json from flat config
npx @oxlint/migrate

After running, a .oxlintrc.json like this will be generated:

json
{
  "$schema": "./node_modules/oxlint/configuration_schema.json",
  "plugins": ["react", "typescript", "oxc"],
  "rules": {
    "react/rules-of-hooks": "error",
    "react/only-export-components": ["warn", { "allowConstantExport": true }]
  }
}

Note — The "typeAware": true option is an alpha feature. Adding it to the options section enables type-aware rules like no-floating-promises, but given its alpha status, separate validation in CI environments is recommended.

After conversion, it's important to check the terminal output for any "unsupported rules" log. Rules not yet supported by Oxlint are automatically skipped, and you'll get a list of what was omitted. One common situation is that presets like plugin:react/recommended don't convert — Oxlint currently doesn't directly support ESLint-style presets. In this case, check the list of missing rules and add the ones you need directly to the rules section.

To verify the conversion looks right, you can run:

bash
# Check full linting results with the converted config
pnpm oxlint .
 
# Check a specific file only
pnpm oxlint src/components/MyComponent.tsx

Example 3: Gradual Migration Running ESLint and Oxlint in Parallel

This is the most realistic scenario in practice. It's useful when your team has custom ESLint plugins or when Oxlint doesn't yet support certain rules.

bash
pnpm add -D eslint-plugin-oxlint
javascript
// eslint.config.mjs
import oxlint from 'eslint-plugin-oxlint'
import reactPlugin from 'eslint-plugin-react'
 
export default [
  {
    plugins: { react: reactPlugin },
    rules: {
      'react/prop-types': 'error',
      'no-unused-vars': 'error',
    },
  },
  // Automatically disables in ESLint the rules that Oxlint covers
  // → prevents both linters from checking the same rule twice
  oxlint.configs['flat/recommended'],
]
json
{
  "scripts": {
    "lint": "oxlint . && eslint ."
  }
}

Oxlint sweeps all files quickly first, and ESLint only handles the rules Oxlint doesn't cover. Honestly, I wondered at first "does running two of them even make sense?" — but since Oxlint processes 95% of files in 0.5 seconds and ESLint only checks the remaining rules, the overall speed improvement is noticeable.

Example 4: Unifying the Entire Toolchain with Vite+

It's still in alpha, but if you want to get a preview of the future direction, here's how to approach it. I tried it first on a side project, and the experience of seeing configuration collapse into a single file was quite impressive.

bash
# Migrate an existing project to Vite+
npx vp migrate

Note — Vite+ is currently in alpha, and the configuration format below may change in the future. Exercise caution when applying it to production-critical projects.

typescript
// vite.config.ts (Vite+ style — lint config is integrated into vite.config)
export default {
  lint: {
    rules: {
      'no-unused-vars': 'error',
      'react/rules-of-hooks': 'error',
    },
  },
}

Trying it on a greenfield project or side project first will let you feel firsthand what an integrated toolchain changes about your actual development flow.


Pros and Cons

Advantages

Item Description
Overwhelming speed 50–100x faster than ESLint. 499ms vs. 31 seconds on M2 Max
Multithreading Maximizes CPU core utilization; the more files, the greater the benefit
Gradual migration possible Can coexist with ESLint; no need to change everything at once
Automatic migration tool @oxlint/migrate auto-converts flat config
Zero-config start Works immediately on defaults even without .oxlintrc.json
Type-aware linting Type-based rules 8–12x faster than typescript-eslint (alpha)
Lower CI costs Less linting time means lower overall CI pipeline execution costs

Disadvantages and Caveats

Item Description Mitigation
No plugin preset support Cannot directly use presets like plugin:react/recommended Specify needed rules individually in the rules section of .oxlintrc.json
ESLint v8 legacy config Cannot auto-convert .eslintrc.* files Convert to flat config with npx @eslint/migrate-config first, then migrate
Some rules unsupported 695+ supported, but doesn't cover the entire ESLint ecosystem Check migration log for missing rules, then run in parallel with eslint-plugin-oxlint
JS Plugin is alpha ESLint-compatible JS plugin support went alpha in March 2026 Check Oxlint compatibility for your plugins before adopting
Type-aware linting is alpha Powerful rules like no-floating-promises require enabling an alpha feature Risk of intermittent CI failures; recommend thorough local validation before enabling
Vite+ is alpha The entire vp CLI is still in alpha Test on side projects first before considering production adoption

Type-Aware Linting — A linting approach that analyzes TypeScript type information to catch patterns that could lead to runtime errors. The rule no-floating-promises (detecting Promises that flow through without await) is a representative example. Because it's an alpha feature and can intermittently fail in CI, the recommendation is to enable it locally first, confirm stability, then activate it in CI.

The Most Common Mistakes in Practice

  1. Passing ESLint v8 config directly to @oxlint/migrate — @oxlint/migrate cannot read .eslintrc.js files. You must convert to flat config with npx @eslint/migrate-config before proceeding with the Oxlint migration.

  2. Skipping the missing rules log — After running @oxlint/migrate, an "unsupported rules" list is printed. Ignoring it means rules that ESLint used to catch quietly disappear. It's strongly recommended to check whether any missing rules are important to your team.

  3. Running both linters without eslint-plugin-oxlint — Running ESLint and Oxlint together without this plugin means the same rules get checked twice. Not only does this cut the speed benefit in half, but duplicate error messages can be confusing.


Closing Thoughts

The habit of waiting 30 seconds is gone. That's the most honest summary of my experience. When linting is fast, you unconsciously check results after saving more often, and over time that builds into a habit of catching bugs much earlier. You don't have to change everything right now. Start small, experience the speed difference firsthand, and then decide whether to commit to a full migration.

Three steps you can start right now:

  1. Try it with the plugin — Install pnpm add -D vite-plugin-oxlint, add the plugin to vite.config.ts, and run pnpm build or pnpm dev to see how linting speed changes.

  2. Attempt auto-migration — If you're already on ESLint flat config, run npx @oxlint/migrate to generate .oxlintrc.json, then review the "unsupported rules" output to see if any rules critical to your team are missing.

  3. Choose a strategy and apply to CI — If no rules are missing or they can be replaced, remove ESLint entirely. If there are still uncovered rules, run in parallel with eslint-plugin-oxlint. For the whole team to benefit, also update the linting script in your CI pipeline at this stage.


References

  • Oxlint Official Documentation
  • Migrate from ESLint | Oxlint Official Guide
  • vite-plugin-oxlint GitHub
  • Announcing Vite+ Alpha | VoidZero
  • Oxlint JS Plugins Alpha (2026-03-11)
  • Announcing Oxlint Type-Aware Linting | VoidZero
  • How to Gradually Migrate from ESLint to Oxlint
  • Speed kills: It's time to retire ESLint and migrate to Oxlint | LogRocket
  • Oxlint bench-linter GitHub (Official Benchmark)
  • I Tried Vite+ and Replaced My Entire Frontend Toolchain | DEV
#Oxlint#ESLint#Vite#TypeScript#React#Rust#마이그레이션#린팅#CI-CD#OXC
Share

Table of Contents

Core ConceptsWhy Is Oxlint So FastOxlint's Place in the Vite EcosystemThe Difference Between @oxlint/migrate and eslint-plugin-oxlintPractical ApplicationExample 1: Integrating with vite-plugin-oxlint in 5 MinutesExample 2: Auto-Converting ESLint Config with @oxlint/migrateExample 3: Gradual Migration Running ESLint and Oxlint in ParallelExample 4: Unifying the Entire Toolchain with Vite+Pros and ConsAdvantagesDisadvantages and CaveatsThe Most Common Mistakes in PracticeClosing ThoughtsReferences

Recommended Posts

Tailwind v4 @container — Responsive Components That Hold Up in Sidebars and Grids Alike
frontend

Tailwind v4 @container — Responsive Components That Hold Up in Sidebars and Grids Alike

At some point in responsive UI work, you hit a wall. A card component you carefully crafted works perfectly in the main feed, but the moment you drop the same c...

June 23, 202618 min read
TanStack DB in Practice: How a Client-Side DB Changes Optimistic Updates
frontend

TanStack DB in Practice: How a Client-Side DB Changes Optimistic Updates

Honestly, when I first heard "client-side embedded DB," my reaction was "just another buzzword." TanStack Query already handles server state management well — w...

June 23, 202617 min read
Implementing Cloudflare Workers for Edge MFE Orchestration to Reduce TTFB
frontend

Implementing Cloudflare Workers for Edge MFE Orchestration to Reduce TTFB

Honestly, when I first heard about micro-frontends (MFE), my immediate thought was, "Independent deployments per team? Sounds great — but doesn't that mean the ...

June 23, 202623 min read
Fixing Micro Frontend Style Conflicts with CSS @layer | Cascade Layers Isolation Strategy
frontend

Fixing Micro Frontend Style Conflicts with CSS @layer | Cascade Layers Isolation Strategy

When I first worked on a Micro Frontend (MFE) project, I remember being quite surprised to see a button style deployed by Team A showing up altered on Team B's ...

June 23, 202622 min read
We Migrated from Webpack to Rsbuild and Production Builds Got 74% Faster — The Migration Reality and Rspack Pitfalls
frontend

We Migrated from Webpack to Rsbuild and Production Builds Got 74% Faster — The Migration Reality and Rspack Pitfalls

Honestly, my first reaction was "another new build tool?" — same as when Vite came out, same as when Turbopack was announced. But after seeing the case from Ala...

June 23, 202619 min read
View Transitions API — Production Page Transition Animations Without Libraries, After Achieving Baseline 2025
frontend

View Transitions API — Production Page Transition Animations Without Libraries, After Achieving Baseline 2025

Honestly, my first reaction when I saw this API was "this actually works?" The idea of implementing app-like page transitions with just a few lines of CSS and a...

June 7, 202620 min read