Replacing ESLint with Oxlint in an Astro site
How I migrated an Astro blog from ESLint and typescript-eslint to type-aware Oxlint, and why astro check still blocks TypeScript 7 here.
I migrated my Astro blog from ESLint to type-aware Oxlint. The lint half of the TypeScript 7 story carried over cleanly from the Tauri app I migrated earlier; the compiler half stalled on Astro’s own TypeScript gate.
Note
Tested with Astro 7, Oxlint 1.82.0, and TypeScript 6.0.3 on 2026-09-12.
The Problem
This site linted with eslint plus typescript-eslint’s recommended set, six explicitly enabled type-aware rules (no-unsafe-argument, no-unsafe-assignment, no-unsafe-call, no-unsafe-member-access, no-unsafe-return, no-explicit-any), and a dedicated tsconfig.eslint.json to feed the parser type information.
typescript-eslint cannot survive TypeScript 7: the typescript@7 package no longer ships the JS Compiler API its type-aware rules run on. I had already replaced it with Oxlint’s tsgolint-driven type-aware mode in the other repo, so the question here was whether an Astro project with a TypeScript ESLint config (eslint.config.ts) migrates as cleanly.
What Changed
The migration tool accepts the TypeScript config path directly:
pnpm dlx @oxlint/migrate eslint.config.ts --type-aware --details
It produced a .oxlintrc.json with 29 rules and nothing skipped. All six custom type-aware rules exist in Oxlint’s type-aware set; I verified that first with a canary file before touching dependencies, since losing no-unsafe-* coverage would have been a silent security regression:
// canary.ts: 7 errors across 6 rules, proving type-aware coverage
declare function getAny(): any;
export function run() {
const assigned: any = getAny();
getAny();
const member = getAny().prop;
return getAny();
}
export const explicit: any = 1;
The interesting Astro-specific wrinkle was tsconfig.eslint.json. It existed only to give ESLint’s parser a project spanning scripts/, tests/, and src/ for type information. Oxlint ignores it and auto-discovers the nearest tsconfig.json per file, which worked here for one non-obvious reason: Astro’s default root tsconfig.json has no include array, so it defaults to **/* and covers every directory the ESLint config did. If the root tsconfig had been narrowed to src/, type-aware rules would have silently lost coverage outside it. I confirmed with canaries in both src/ and scripts/ before deleting tsconfig.eslint.json.
Two housekeeping notes for anyone doing the same:
- The
.astro/generated directory needs to stay inignorePatterns(the migration tool carried it over automatically). - knip initially flagged
oxlint-tsgolintas needing anignoreDependenciesentry; after the migration it resolves the package through Oxlint’s config, and the entry became a “remove this” hint instead.
The dependency diff removed eslint, typescript-eslint, @typescript-eslint/parser, @typescript-eslint/eslint-plugin, and eslint-config-prettier (Oxlint does no stylistic rules; Prettier keeps that job), and added oxlint 1.82.0 with oxlint-tsgolint 7.0.2001.
Results
Measured on this repo (41 lintable files), before → after:
- Lint:
eslint .2.3s →oxlint~0.3s with type-aware rules active - Lockfile: −500 lines net
- Gates unchanged:
astro check,markdownlint-cli2, Vitest (21 tests), Prettier, and Playwright all pass as before
What TS7 Needs Here
Upgrading the compiler is a separate decision in an Astro project. astro check runs through @astrojs/check, which peers typescript ^5.0.0 || ^6.0.0. That is the same shape of blocker typescript-eslint posed in the other repo: a tool that depends on the JS Compiler API. So this site now runs Oxlint with typescript still at 6.0.3, and the compiler bump waits on Astro shipping TS7 support. When it does, the lint side needs no further changes.
What I’d Do Differently
Nothing went wrong this time, mostly because the first migration taught me the checks: verify rule coverage with canaries before swapping dependencies, and validate the generated JSON for duplicate keys (@oxlint/migrate regenerating a file I had hand-edited caused that once). I also re-ran knip against the stashed pre-migration state to confirm its two remaining findings existed before my changes, which kept the scope of the migration honest.
References
- Oxlint: the Rust linter this migrated to
- Oxlint’s migrate-from-ESLint guide: the
@oxlint/migratetool, including TypeScript config paths - Oxlint type-aware linting: tsgolint-driven type-aware rules
- TypeScript’s native Go port announcement: the TS7 transition and the JS API removal
- typescript-eslint: the tooling this replaced
- Astro CLI reference:
astro checkand its TypeScript dependency - knip: dependency hygiene, and its interaction with Oxlint’s config
- Migrating a Tauri app to TypeScript 7 and Oxlint: companion write-up covering the native compiler setup
This post was written with AI assistance.