writing/tutorial/2026/06
TutorialJun 8, 2026·20 min read

Oxlint: Blazing-Fast Rust Linting for Next.js & TypeScript (2026 Guide)

Learn how to set up Oxlint in a Next.js TypeScript project, configure its 819+ built-in rules, migrate from ESLint, and integrate it into your CI pipeline — all with 50-100x faster lint times.

If your npm run lint takes 20 seconds on a medium-sized Next.js project, you are not alone. ESLint, despite its rich ecosystem, is a JavaScript process — it parses, resolves, and evaluates files one at a time with an overhead that compounds quickly as codebases grow.

Oxlint is a Rust-based linter from the OXC project (backed by VoidZero, Evan You's tooling company). It reached v1.0 stable in August 2025, shipped JS plugin support in alpha in March 2026, and is now used in production at Shopify, Airbnb, Mercedes-Benz, and Zalando. On codebases that previously took minutes to lint, Oxlint completes in under a second.

In this guide you will:

  • Understand how Oxlint achieves its speed advantage
  • Install and configure Oxlint in a Next.js 15 TypeScript project
  • Enable built-in TypeScript, React, and Next.js plugin rules
  • Migrate an existing ESLint config with the official migration tool
  • Add custom rule overrides and per-directory configs
  • Wire everything into a CI/CD pipeline
  • Decide when to keep ESLint alongside Oxlint

Prerequisites

  • Node.js 20+ and pnpm installed
  • A Next.js 15 project with TypeScript (or create one with pnpm create next-app@latest)
  • Basic familiarity with ESLint config files

Why Oxlint Is Different

ESLint runs in a Node.js event loop, serializes plugin calls through JavaScript, and traverses each file's AST with a single thread (unless you manually shard with --max-warnings tricks). It is architected for extensibility, not throughput.

Oxlint is built on the OXC compiler stack, which is written in Rust. Its advantages:

FactorESLintOxlint
LanguageJavaScriptRust
ThreadingSingle threadMulti-threaded (Rayon)
Rule pluginsJavaScriptRust (native) + JS alpha
Config format.eslintrc.* / flat.oxlintrc.json / oxlint.config.ts
Rules countUnlimited (plugins)819+ built-in
Typical speed20-120s on large appsunder 1s

The 50-100x speed claim is not marketing: it comes from parallelising file parsing across CPU cores in native code, with zero serialisation overhead between the parser and rule evaluator.


Step 1: Install Oxlint

pnpm add -D oxlint

That's it. Oxlint ships as a single self-contained binary — no peer deps, no plugin packages to resolve.

Run it immediately without any config to see your project's current state:

pnpm oxlint .

You'll see output like:

✖ 3 errors and 12 warnings found.
  src/app/page.tsx:14:5  error  no-unused-vars  'handler' is defined but never used
  src/lib/utils.ts:7:1   warn   no-console      Unexpected console statement
  ...

Step 2: Create the Configuration File

Oxlint accepts two config formats. The simplest is .oxlintrc.json at your project root.

Create it:

{
  "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/crates/oxc_linter/src/config/schema.json",
  "plugins": ["typescript", "react", "nextjs", "jsx-a11y", "import"],
  "env": {
    "browser": true,
    "node": true,
    "es2022": true
  },
  "rules": {
    "no-console": "warn",
    "no-unused-vars": "error",
    "typescript/no-explicit-any": "warn",
    "react/jsx-key": "error",
    "nextjs/no-img-element": "error"
  }
}

Available built-in plugins

All of the following plugins are implemented natively in Rust — no npm packages needed:

  • typescript — ports rules from @typescript-eslint/eslint-plugin
  • react — ports rules from eslint-plugin-react and eslint-plugin-react-hooks
  • nextjs — ports Next.js-specific rules (image, script, font, link anti-patterns)
  • jsx-a11y — ports accessibility rules from eslint-plugin-jsx-a11y
  • import — ports import ordering and resolution rules
  • unicorn — ports code quality rules from eslint-plugin-unicorn
  • jest / vitest — test file rules
  • oxc — OXC-specific additional rules

Step 3: Use the TypeScript Config (oxlint.config.ts)

For larger projects, the TypeScript config format gives you type-safe rule definitions and overrides per directory:

// oxlint.config.ts
import { defineConfig } from "oxlint";
 
export default defineConfig({
  plugins: ["typescript", "react", "nextjs", "jsx-a11y", "import"],
  env: {
    browser: true,
    node: true,
    es2022: true,
  },
  rules: {
    "no-console": "warn",
    "no-debugger": "error",
    "no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
    "typescript/no-explicit-any": "warn",
    "typescript/consistent-type-imports": "error",
    "react/jsx-key": "error",
    "react/no-unknown-property": "error",
    "nextjs/no-img-element": "error",
    "nextjs/no-html-link-for-pages": "error",
    "import/no-duplicates": "error",
  },
  overrides: [
    {
      files: ["**/*.test.ts", "**/*.spec.ts", "**/*.test.tsx"],
      plugins: ["vitest"],
      rules: {
        "vitest/no-disabled-tests": "warn",
        "vitest/prefer-expect-assertions": "warn",
        "no-console": "off",
      },
    },
    {
      files: ["scripts/**/*.ts"],
      rules: {
        "no-console": "off",
        "typescript/no-explicit-any": "off",
      },
    },
  ],
});

The overrides array lets you apply different rule sets to test files, scripts, or any glob pattern — the same pattern ESLint users will recognise.


Step 4: Add npm Scripts

Update your package.json:

{
  "scripts": {
    "lint": "oxlint --fix-suggestions src",
    "lint:fix": "oxlint --fix src",
    "lint:ci": "oxlint --format github src"
  }
}

Key flags:

FlagBehaviour
--fixAuto-fix fixable violations
--fix-suggestionsShow suggested fixes without applying
--format githubGitHub Actions annotation format
--format jsonMachine-readable output for tooling
--deny-warningsTreat warnings as errors (good for CI)
--max-warnings NFail if more than N warnings
-c pathUse a specific config file

Step 5: Migrate From ESLint

If you have an existing ESLint config, use the official migration tool to generate an equivalent .oxlintrc.json:

pnpm dlx @oxlint/migrate

It reads your .eslintrc.* or eslint.config.js, maps rules to their Oxlint equivalents, and writes .oxlintrc.json. Rules without a native Oxlint equivalent are listed in a summary so you know what remains ESLint-only.

Typical output:

✔ Migrated 34 rules to .oxlintrc.json
⚠ 6 rules have no Oxlint equivalent:
  - import/order (custom sort config)
  - @typescript-eslint/no-floating-promises
  - ...
ℹ Run ESLint only for the 6 remaining rules using the eslint-only config.

For the unmigrated rules, run both linters in your lint script. Because Oxlint runs in under a second, the combined time is still far less than ESLint alone:

{
  "scripts": {
    "lint": "oxlint src && eslint src --config eslint-remaining.config.js"
  }
}

The Oxlint docs recommend disabling any ESLint rule that Oxlint already handles, to avoid duplicate diagnostics.


Step 6: Next.js-Specific Rules

Oxlint ships built-in rules for Next.js anti-patterns. Enable the nextjs plugin in your config and you get:

RuleWhat it catches
nextjs/no-img-elementRaw img tags (use next/image)
nextjs/no-html-link-for-pagesRaw a tags for internal links (use next/link)
nextjs/no-script-in-headscript inside next/head
nextjs/no-sync-scriptsSync script tags blocking render
nextjs/no-typosTypos in Next.js data-fetching exports
nextjs/next-script-for-gaUsing script for GA instead of next/script

Example in practice — Oxlint will flag this:

// ❌ Triggers nextjs/no-img-element
export default function Hero() {
  return <img src="/hero.jpg" alt="Hero" />;
}

Fix:

// ✅ Correct
import Image from "next/image";
 
export default function Hero() {
  return <Image src="/hero.jpg" alt="Hero" width={1200} height={600} />;
}

Step 7: TypeScript Rules

The typescript plugin ports the most-used rules from @typescript-eslint. Some highlights:

{
  "rules": {
    "typescript/no-explicit-any": "warn",
    "typescript/no-unused-vars": "error",
    "typescript/consistent-type-imports": "error",
    "typescript/no-non-null-assertion": "warn",
    "typescript/prefer-as-const": "error",
    "typescript/no-empty-interface": "warn",
    "typescript/no-inferrable-types": "warn"
  }
}

Note that consistent-type-imports is a powerful rule for TypeScript codebases — it enforces import type { Foo } syntax, which the TypeScript compiler can elide more efficiently and which prevents import-cycle issues:

// ❌ Flagged by typescript/consistent-type-imports
import { User } from "./types";
 
// ✅ Correct
import type { User } from "./types";

Step 8: React Hooks Rules

The react plugin includes hooks rules. These are critical in Next.js App Router projects where components switch between Server and Client components:

{
  "rules": {
    "react/rules-of-hooks": "error",
    "react/exhaustive-deps": "warn",
    "react/jsx-key": "error",
    "react/no-unknown-property": "error"
  }
}

rules-of-hooks catches hooks called conditionally or inside loops — one of the most common runtime bugs in React apps.


Step 9: CI/CD Integration

GitHub Actions

# .github/workflows/lint.yml
name: Lint
 
on: [push, pull_request]
 
jobs:
  oxlint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - name: Run Oxlint
        run: pnpm oxlint --format github --deny-warnings src

The --format github flag emits annotations directly into the PR diff view — violations appear as inline comments on the relevant line, exactly like ESLint with the eslint-formatter-github package.

GitLab CI

oxlint:
  stage: validate
  image: node:20-alpine
  script:
    - corepack enable
    - pnpm install --frozen-lockfile
    - pnpm oxlint --deny-warnings src
  cache:
    key: pnpm-$CI_COMMIT_REF_SLUG
    paths:
      - node_modules/.pnpm-store

Because Oxlint typically finishes in under one second, it adds negligible time to your pipeline stage.


Step 10: VS Code Integration

Install the Oxlint VS Code extension to see lint errors inline as you type.

Add to your .vscode/settings.json:

{
  "oxc.enable": true,
  "oxc.configPath": ".oxlintrc.json",
  "[typescript]": {
    "editor.defaultFormatter": "biomejs.biome"
  }
}

Oxlint integrates alongside Biome (for formatting) and the TypeScript language server — they each handle their own concern without conflict.


Step 11: JS Plugins Alpha (March 2026)

The JS plugins alpha released in March 2026 allows writing Oxlint plugins in JavaScript/TypeScript — the same plugin API familiar to ESLint users. This is a bridge for teams whose ESLint plugins have no Rust equivalent yet.

Enable a JS plugin:

{
  "plugins": ["typescript", "react"],
  "jsPlugins": ["./my-custom-rules.mjs"]
}

A simple JS plugin:

// my-custom-rules.mjs
export default {
  rules: {
    "no-direct-fetch": {
      meta: {
        type: "suggestion",
        docs: { description: "Prefer the api() helper over raw fetch()" },
      },
      create(context) {
        return {
          CallExpression(node) {
            if (
              node.callee.type === "Identifier" &&
              node.callee.name === "fetch"
            ) {
              context.report({
                node,
                message: "Use the api() helper instead of raw fetch().",
              });
            }
          },
        };
      },
    },
  },
};

Note: JS plugin execution is still slower than native Rust rules, but because Oxlint handles the bulk of rules natively, the total time remains far less than ESLint alone.


Benchmarks: Real-World Numbers

On a Next.js app with 1,200 TypeScript files:

ToolTime
ESLint (flat config, no cache)42 seconds
ESLint (with cache)8 seconds
Oxlint (cold)0.7 seconds
Oxlint (warm)0.5 seconds

These are representative numbers from teams that have published migration posts. Your numbers will vary based on hardware, file count, and rule set.


When to Keep ESLint

Oxlint does not yet cover every ESLint rule or plugin. Keep ESLint (scoped to only the gaps) when you need:

  • @typescript-eslint/no-floating-promises — async error catching
  • Complex import/order with custom sort groups
  • Custom in-house ESLint plugins not yet ported
  • eslint-plugin-testing-library for React Testing Library

The recommended strategy in 2026 is Oxlint first, ESLint for the remainder — run them sequentially. The combined time is still 5-10x faster than ESLint alone.


Troubleshooting

"Rule X not found"

Oxlint rule names include the plugin prefix. Use typescript/no-explicit-any, not @typescript-eslint/no-explicit-any.

"File ignored by .gitignore"

By default Oxlint respects .gitignore. Pass --no-ignore to override, or add explicit ignore patterns in .oxlintrc.json:

{
  "ignore": ["dist/**", ".next/**", "node_modules/**"]
}

Conflicting rules with ESLint

When running both, disable the Oxlint-equivalent rules in your ESLint config to avoid duplicate reports:

// eslint.config.js
export default [
  {
    rules: {
      // Handled by Oxlint — disable here
      "no-unused-vars": "off",
      "no-console": "off",
      "react/jsx-key": "off",
    },
  },
];

Next Steps

  • Read the full rule reference to discover all 819+ available rules
  • Explore Biome for a complementary Rust-powered formatter (Oxlint lints; Biome formats)
  • Set up Prettier + Oxlint if your team prefers Prettier for formatting
  • Try the Oxlint playground to test rules on snippets without a project

Conclusion

Oxlint brings sub-second linting to JavaScript and TypeScript projects that previously suffered through tens of seconds of ESLint delay on every save and CI run. With 819+ built-in rules covering TypeScript, React, Next.js, accessibility, and imports — all implemented natively in Rust — most Next.js projects can replace the bulk of their ESLint config today with zero performance regression and dramatic speed gains.

The migration path is clear: install Oxlint, run @oxlint/migrate, handle the small set of unmigrated rules with a lean ESLint fallback, and enjoy lint times measured in milliseconds rather than seconds.

Your developers will notice. Your CI pipeline will thank you.