---
name: nextjs-oxc
description: Configure la toolchain oxc (Oxlint + Oxfmt + Knip) dans un projet Next.js 16 — installe les dépendances, ajoute les scripts package.json, génère les configs (`.oxlintrc.json`, `eslint.config.mjs`, `knip.json`). À utiliser dès que l'utilisateur veut intégrer / installer / setup oxc / oxlint / oxfmt / knip / linter Rust / formatter Rust / dead code elimination dans un projet Next.js, ou parle de remplacer ESLint/Prettier par la stack oxc.rs. NE PAS toucher au bundle (Turbopack reste sur SWC, oxc-minify alpha hors scope).
---

# nextjs-oxc

Setup de la toolchain **oxc** (Oxlint + Oxfmt + Knip) dans un projet Next.js 16, alignée avec `global-rules.md` §2 (Lint/Format/Minify via oxc, dead code via knip).

## Périmètre

| Couche | Outil | Inclus |
|--------|-------|--------|
| Lint | **Oxlint** + `eslint-plugin-oxlint` + `@next/eslint-plugin-next` (ESLint en complément pour les rules absentes) | ✅ |
| Format | **Oxfmt** (remplace Prettier + plugins tailwind/imports) | ✅ |
| Dead code | **Knip v6** (fichiers/exports/deps inutilisés) | ✅ |
| Minify bundle | ❌ Pas touché — Turbopack utilise SWC, oxc-minify est alpha et non-pluggable côté bundler |

## Pré-requis

- Projet Next.js 16 (vérifier `package.json` → `next` en `^16.x`).
- Package manager : `pnpm` (default), fallback `bun`/`npm`/`yarn` détecté via lockfile.

## Procédure

1. **Vérifier le projet.** Lire `package.json`. Si `next` n'est pas en dépendance ou < 16, demander confirmation à l'utilisateur.

2. **Détecter le package manager** via lockfile :
   - `pnpm-lock.yaml` → `pnpm`
   - `bun.lock` → `bun`
   - `package-lock.json` → `npm`
   - `yarn.lock` → `yarn`
   - Aucun → demander à l'utilisateur.

3. **Si projet hérite de `next lint`** (Next 16 l'a supprimé), proposer le codemod **avant** d'installer :
   ```bash
   npx @next/codemod@canary next-lint-to-eslint-cli .
   ```
   Skip si l'utilisateur a déjà migré (présence d'un `eslint.config.{js,mjs,ts}` flat config).

4. **Installer les dépendances** (en dev) :
   ```bash
   <pm> add -D oxlint oxfmt knip eslint eslint-plugin-oxlint @next/eslint-plugin-next
   ```

5. **Générer les configs** (créer si absent, ne pas écraser sans demander) :
   - `.oxlintrc.json` — config Oxlint (cf. template ci-dessous)
   - `.oxfmtrc.json` — config Oxfmt avec `sortImports`/`sortTailwindcss`/`sortPackageJson` activés
   - `eslint.config.mjs` — flat config qui désactive dans ESLint les règles déjà couvertes par Oxlint
   - `knip.json` — entries Next.js 16 (`proxy.{js,ts}` remplace `middleware.{js,ts}` qui est déprécié)

6. **Ajouter les scripts dans `package.json`** (merge avec l'existant, ne pas écraser) :
   ```json
   {
     "scripts": {
       "lint": "oxlint && eslint .",
       "lint:fix": "oxlint --fix && eslint . --fix",
       "format": "oxfmt .",
       "format:check": "oxfmt --check .",
       "deadcode": "knip",
       "deadcode:fix": "knip --fix",
       "clean": "<pm> run format && <pm> run lint:fix && <pm> run deadcode:fix",
       "ci": "<pm> run format:check && <pm> run lint && <pm> run deadcode"
     }
   }
   ```
   Remplacer `<pm>` par le package manager détecté.

7. **Premier pass** (optionnel, demander avant de lancer) :
   ```bash
   <pm> run format && <pm> run lint:fix
   ```
   Ne PAS lancer `deadcode:fix` automatiquement — Knip a des faux positifs sur les entries dynamiques Next.js (sitemap.ts, opengraph-image.tsx, etc.). Laisser l'utilisateur reviewer.

8. **Confirmer.** Lister à l'utilisateur :
   - Fichiers créés / modifiés
   - Scripts ajoutés
   - Prochaines étapes : reviewer les findings Knip, brancher en CI, optionnellement ajouter un pre-commit lefthook.

## Templates de configs

### `.oxlintrc.json`

```json
{
  "$schema": "./node_modules/oxlint/configuration_schema.json",
  "plugins": [
    "eslint",
    "react",
    "react-perf",
    "unicorn",
    "typescript",
    "nextjs",
    "import",
    "jsx-a11y",
    "oxc"
  ],
  "categories": {
    "correctness": "error",
    "suspicious": "warn",
    "perf": "warn",
    "pedantic": "off",
    "style": "off",
    "restriction": "off",
    "nursery": "off"
  },
  "ignorePatterns": [
    ".next/**",
    "dist/**",
    "build/**",
    "node_modules/**",
    "public/**",
    "*.config.*"
  ]
}
```

### `.oxfmtrc.json`

Defaults Prettier-compatibles (singleQuote `false`, semi `true`, trailingComma `"all"`) + sort imports / Tailwind / package.json activés. Réf : https://oxc.rs/docs/guide/usage/formatter/config.html

```json
{
  "$schema": "./node_modules/oxfmt/configuration_schema.json",
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": false,
  "trailingComma": "all",
  "sortImports": {
    "groups": [
      "type",
      "builtin",
      "external",
      ["internal", "subpath"],
      ["parent", "sibling", "index"],
      "style",
      "unknown"
    ],
    "internalPattern": ["~/", "@/", "#"],
    "newlinesBetween": true,
    "order": "asc",
    "ignoreCase": true
  },
  "sortTailwindcss": {
    "attributes": ["className", "class"],
    "functions": ["clsx", "cn", "cva", "tw"]
  },
  "sortPackageJson": true,
  "ignorePatterns": [".next/**", "dist/**", "build/**", "node_modules/**", "public/**"]
}
```

**Tailwind config path (optionnel)** : si le projet utilise Tailwind v3, ajouter `"config": "tailwind.config.js"` dans `sortTailwindcss`. Tailwind v4 (CSS-first) : ajouter `"stylesheet": "app/globals.css"` (chemin de l'import `@import "tailwindcss"`). Sans rien, oxfmt utilise la liste d'utilities par défaut.

### `eslint.config.mjs`

```js
import oxlint from 'eslint-plugin-oxlint';
import nextPlugin from '@next/eslint-plugin-next';

export default [
  {
    plugins: { '@next/next': nextPlugin },
    rules: {
      ...nextPlugin.configs.recommended.rules,
      ...nextPlugin.configs['core-web-vitals'].rules,
    },
  },
  // Désactive dans ESLint toutes les règles déjà couvertes par Oxlint — pas de double-check.
  ...oxlint.configs['flat/recommended'],
  ...oxlint.configs['flat/nextjs'],
  ...oxlint.configs['flat/react'],
  ...oxlint.configs['flat/typescript'],
];
```

### `knip.json`

```jsonc
{
  "$schema": "https://unpkg.com/knip@6/schema.json",
  "next": {
    "entry": [
      "next.config.{js,ts,mjs}",
      "middleware.{js,ts}",
      "proxy.{js,ts}",
      "app/**/{layout,page,loading,error,not-found,route,template,default,opengraph-image,twitter-image,icon,apple-icon,sitemap,robots}.{js,ts,tsx,mdx}",
      "instrumentation.{js,ts}"
    ]
  },
  "ignore": [".next/**", "dist/**", "build/**"]
}
```

## Hors-scope (intentionnellement)

- **oxc-minify dans le bundle** : Turbopack (Next 16) est verrouillé sur SWC. Ne pas tenter d'injecter oxc-minify côté bundler — alpha + non-pluggable.
- **`unplugin-oxc`** : pas compatible Turbopack. Réservé aux libs publiées séparément (Rolldown).
- **Mode `--webpack`** : déprécié dans Next 16. Ne pas pousser dans cette direction.
- **Pre-commit hook (lefthook)** : optionnel, mentionner mais ne pas installer sans demande explicite.
- **Type-aware Oxlint (`oxlint-tsgolint`)** : technical preview, ne pas activer par défaut.

## Pièges à signaler à l'utilisateur

- **`next build` ne lint plus** depuis Next 16 (`next lint` supprimé). Si la CI repose sur "build fail-on-lint", elle ne fail plus → bien câbler `<pm> run lint` en step séparé.
- **Knip false positives** sur les entries Next dynamiques (sitemap.ts, opengraph-image.tsx, etc.). Toujours review manuel avant `deadcode:fix`.
- **`proxy.{js,ts}`** remplace `middleware.{js,ts}` (déprécié) en Next 16 — le `knip.json` inclut les deux pour transition.

## Pourquoi ce design

- **Stratégie 1 (Oxlint + ESLint)** plutôt que Oxlint seul : le plugin Next.js complet vit côté ESLint, et certaines rules type-aware (react-hooks, etc.) ne sont pas dans Oxlint stable. Le pre-pass Oxlint reste 50-100× plus rapide → CI accélérée sans sacrifier la couverture.
- **Knip à part** : Oxlint détecte le mort intra-fichier, Knip le mort cross-fichier (exports/files/deps inutilisés). Couches orthogonales, complémentaires.
- **Pas d'over-engineering** : 3 outils, 3 configs, 6 scripts. Tout le reste (oxc-minify, unplugin-oxc, tsgolint) est hors-scope tant qu'il n'est pas GA.
