Next.js

Instalé TypeScript 7 y ESLint dejó de arrancar: no hay versión que lo arregle

Autorangel cruz
Actualizado
Publicado
Lectura7 min de lectura
Instalé TypeScript 7 y ESLint dejó de arrancar: no hay versión que lo arregle

El typecheck bajó de 3,15 segundos a 0,69. El build de Next pasó de 3,3 segundos a 378 milisegundos typechequeando. Tres de mis cuatro gates de CI en verde y más rápidos que nunca.

El cuarto no arranca, y no hay nada que instalar para arreglarlo.

Cómo llegué aquí

Next.js 16.3 salió recomendando TypeScript 7, que es el port nativo del compilador. Sus propios docs son escuetos sobre lo que hay que hacer:

TypeScript 7 does not currently provide the JavaScript compiler API. To use TypeScript 7 during next build, install it in your project.

pnpm add -D typescript@^7

Eso es todo. Next usa el CLI local de tsc por defecto, así que no hay configuración extra. Lo instalé, corrí los gates y salió esto:

typecheck  ✓  0.69s
test       ✓  104 passed
build      ✓  TypeScript en 378ms
lint       ✗

El error dice exactamente lo que pasa

typescript-eslint does not support TS 7.0.
Please see .../#running-side-by-side-with-typescript-6.0 to run
typescript-eslint using the TS 6 API.
See also .../issues/10940 for tracking typescript-eslint's
support for TS >=7.1

Fíjate en la frase de los docs de Next otra vez, porque es la causa entera: no expone API JavaScript. TypeScript 7 hoy existe solo como binario de línea de comandos.

Y un linter de TypeScript no puede trabajar con un binario. Necesita entrar al compilador por código: recorrer el AST, preguntar el tipo de una expresión, resolver un símbolo. Eso es la API JavaScript, y en la 7 todavía no está. La API estable programática está planificada para 7.1, no para 7.0.

Lo importante es que esto no afecta solo al linter. Afecta a todo lo que lee tipos programáticamente, y hay issues abiertos por el mismo motivo aguas arriba, como el de ts-api-utils, la librería sobre la que typescript-eslint construye sus checks de tipos.

No es un rango de peer desactualizado

Mi primera reacción fue la obvia: el peer estará mal puesto, instalo una versión nueva y listo. No.

$ npm view typescript-eslint@8.66.0 peerDependencies
{ "typescript": ">=4.8.4 <6.1.0" }
 
$ npm view typescript-eslint@8.66.1-alpha.0 peerDependencies
{ "typescript": ">=4.8.4 <6.1.0" }

Ni latest ni canary mueven ese tope. Y no es cosa del día que escribí esto: lo volví a comprobar tres semanas después y sigue igual, con las versiones ya avanzadas.

Canal Versión Peer de typescript ¿Entra la 7.0.2?
latest 8.68.0 >=4.8.4 <6.1.0 no
canary 8.68.1-alpha.6 >=4.8.4 <6.1.0 no

Dos números que conviene mirar juntos: el tope sigue en <6.1.0 y TypeScript va por la 7.0.2 en latest, con la 7.1 ya en next. La distancia no se está cerrando, se está abriendo.

Y el rechazo no vive en el paquete que importas, vive en el core compartido. Lo comprobé cargando cada entrada por separado:

$ node -e "import('@typescript-eslint/parser')"
FAIL: typescript-eslint does not support TS 7.0.
 
$ node -e "import('@typescript-eslint/eslint-plugin')"
FAIL: typescript-eslint does not support TS 7.0.

El issue que pide soporte para 7.0.2, el 12518, está cerrado como not planned. Y no fue un descuido: alguien volvió a pedirlo en el 12720, "Add support for Typescript 7", y lo cerraron igual, not planned, el 18 de agosto de 2026. Dos peticiones, el mismo criterio, con ocho días de diferencia. Es una postura, no una cola de trabajo.

El que sí sigue abierto es el 10940, el de adoptar el compilador nativo (tsgo) para la información de tipos. Lleva abierto desde marzo de 2025 y su última actividad es de julio de 2026. Está bloqueado por algo que no depende de typescript-eslint: ESLint no soporta parsers asíncronos, y el compilador nativo se consume por binding nativo o WASM, que es asíncrono.

Hay un detalle que lo remata. El issue 12601, "use typescript 7 for typechecking", está cerrado como completado: el proyecto usa TypeScript 7 para revisar los tipos de su propio repositorio. Pueden compilar con la 7, pero no pueden soportarla para quien los instala, porque una cosa es correr el compilador y otra entrar en él por API.

O sea que no hay fecha. No es "espera al martes".

Los cinco intentos con pnpm

Si TypeScript 7 tiene que estar en la raíz y ESLint necesita un 6, la idea evidente es darle un 6 solo a ESLint. pnpm tiene mecanismos para eso. Probé todos y anoto por qué fallan, porque el patrón es el mismo y explica bastante.

1. packageExtensions con dependencies. Inyectar un typescript real dentro de cada paquete de typescript-eslint, apuntando al shim de la 6:

packageExtensions:
  "@typescript-eslint/typescript-estree":
    dependencies:
      typescript: npm:@typescript/typescript6@^6.0.2

El symlink acabó apuntando a la 7 igual:

$ readlink node_modules/.pnpm/typescript-eslint@.../node_modules/typescript
../../typescript@7.0.2/node_modules/typescript

2. packageExtensions con peerDependencies, para corregir el rango declarado. Tampoco cambia la resolución.

3. overrides por rango. Este casi funciona, y su forma de fallar es la más instructiva:

overrides:
  "typescript@>=4.8.4 <6.1.0": npm:@typescript/typescript6@^6.0.2

Alcanzó una instancia, pero no la que eslint-config-next empaqueta. Ese paquete declara typescript: >=3.3.1, un rango que acepta la 7, así que se llevaba el compilador de la raíz. Al añadir ese segundo rango al override desapareció TypeScript 7 del proyecto entero: el selector matchea por versión resuelta, no por el string declarado, y 7.0.2 satisface >=3.3.1. No hay forma de distinguir el peer laxo de la dependencia de la raíz.

4. overrides con padre>hijo, incluido el padre correcto:

overrides:
  "eslint-config-next>typescript": npm:@typescript/typescript6@^6.0.2

Nada.

5. Quitar eslint-config-next y declarar los seis plugins directos, para que exista una sola copia de typescript-eslint y el override la alcance. Al ser dependencia directa de la raíz, su peer se resolvió a la 7 de la raíz. Peor punto de partida que el inicial.

La razón única detrás de los cinco: los peer dependencies se resuelven desde quien importa, no desde quien los declara. Los overrides y las packageExtensions actúan sobre dependencias normales. Un peer siempre acaba mirando al importador, y el importador aquí es la raíz, donde vive TypeScript 7. Ninguna capa de configuración de pnpm se interpone ahí.

Las dos salidas que quedan

Ni una es bonita.

La primera es el shim oficial. Microsoft publica @typescript/typescript6, que reexporta la API de la 6, y documenta instalarlo lado a lado:

{
  "devDependencies": {
    "@typescript/native": "npm:typescript@^7.0.2",
    "typescript": "npm:@typescript/typescript6@^6.0.2"
  }
}

El nombre typescript pasa a ser el shim, que es lo que ESLint necesita, y la 7 entra bajo otro nombre aportando el binario tsc. Funciona: los cuatro gates en verde, y la 7 sigue typechequeando todo.

El precio es que tu package.json deja de decir la verdad de un vistazo. Alguien que lo abra lee typescript: @typescript/typescript6 y concluye que el proyecto está en la 6. Y en Next hay un detalle extra: su checker busca bin.tsc dentro del paquete typescript, y el shim expone tsc6, así que el build no arranca sin apagar el checker interno y encadenar tsc --noEmit en el script.

La segunda es aceptar que no hay linter. TypeScript 7 pelado, package.json honesto, y el gate de lint fuera hasta que upstream se ponga al día.

Elegí esta. El proyecto es un blog estático con typecheck estricto y 104 tests, y el typecheck es el que caza lo que de verdad rompe producción. Perder las reglas de @typescript-eslint durante unas semanas duele menos que dejar en el repo un package.json que miente sobre su propia versión de TypeScript. Lo dejé escrito en el workflow, porque un paso ausente sin explicación se lee como un olvido:

# FALTA EL PASO DE LINT, y no es un olvido: el proyecto usa TypeScript 7,
# que todavía no expone API JavaScript (solo CLI). typescript-eslint
# depende de esa API, así que aborta al ver un 7.x y ESLint no puede ni
# parsear los .ts.

Conclusión

La pregunta útil antes de subir a TypeScript 7 no es si tu código compila. Compila. Es qué herramientas de tu stack leen tipos por código, porque esas son las que se caen: linters, transformers de AST, cualquier cosa construida sobre la API del compilador.

Si tu respuesta es "solo tsc", sube hoy y disfruta los 4x. Si tienes un linter con reglas type-aware, y casi cualquier proyecto TypeScript serio lo tiene, la decisión no es técnica: es si prefieres un package.json raro o un gate de calidad menos, hasta que llegue la 7.1.

Que es, por cierto, un buen recordatorio de que "10 veces más rápido" nunca es el coste total de una migración.

Si quieres seguir el hilo: el anuncio de TypeScript 7.0, los docs de Next sobre TypeScript, y los issues typescript-eslint#10940, #12518 y #12720.

¿Tienes un proyecto en mente?

Trabajo con Laravel, WordPress, SEO técnico y servidores MCP. El primer paso es una llamada de descubrimiento, sin costo ni compromiso, donde me cuentas qué necesitas y te digo con honestidad si puedo ayudarte.

Hablemos