TypeScript strict mode: las 6 opciones del tsconfig que más impactan en producción y cuándo activarlas
Hay una escena que se repite. Alguien configura un proyecto nuevo, le dice a todo el mundo "usamos TypeScript estricto" y agrega strict: true al tsconfig.json. Todos asienten. El CI compila. Y tres meses después aparece un bug en producción que TypeScript podría haber atrapado si hubieran activado noUncheckedIndexedAccess.
Mi tesis es directa: strict: true es un atajo cómodo que activa seis flags razonables pero deja afuera dos opciones que, en mi criterio, previenen más bugs silenciosos que la mitad del grupo base. El problema no es strict: true en sí — es que la mayoría lo activa y siente que ya terminó.
Este post no es "activá strict y listo". Es un análisis bandera por bandera: qué hace cada una, qué tipo de error previene y cuál es el orden sensato para migrar una codebase que todavía no las tiene todas activadas.
Qué incluye strict: true — y qué no
Según la es clara en esto. ¿Por qué no está en strict? Porque genera muchos errores en codebases existentes donde el acceso por índice es ubicuo y nadie lo valida. Pero eso no lo hace opcional si querés cobertura real.
En escenarios con Prisma y resultados de queries, con respuestas de APIs externas casteadas a arrays, o con configuraciones leídas de JSON — este flag atrapa exactamente la clase de bug que aparece tarde, en producción, cuando el array llega vacío por primera vez.
6. exactOptionalPropertyTypes — el más subvalorado de todos
Este es el segundo que más gente ignora y el que más sutilmente rompe cosas. Sin este flag, TypeScript trata undefined como un valor válido para una propiedad opcional. Con él, hay una diferencia entre "la propiedad puede no estar" y "la propiedad está y vale undefined".
interface Config {
timeout?: number; // propiedad opcional
}
// Sin exactOptionalPropertyTypes:
// Estas dos asignaciones son equivalentes para TypeScript:
const a: Config = {}; // timeout no existe
const b: Config = { timeout: undefined }; // timeout existe pero es undefined
// Con exactOptionalPropertyTypes:
const c: Config = { timeout: undefined }; // ❌ Error
// Type 'undefined' is not assignable to type 'number'
// porque 'timeout?' significa 'puede no estar', no 'puede ser undefined'
¿Por qué importa? Porque hay una diferencia operacional entre una clave ausente y una clave con valor undefined. En serialización JSON, en spreads de objetos, en Prisma updates — el comportamiento difiere. exactOptionalPropertyTypes hace que TypeScript entienda esa diferencia.
El orden para migrar una codebase existente
Si estás agregando esto a un proyecto que ya tiene código, el orden sensato es:
Paso 1: strictNullChecks → más errores, más impacto, pero son los más urgentes
Paso 2: noImplicitAny → segundo lote de errores, más fáciles de resolver
Paso 3: strict: true → activa el resto del grupo base de golpe
Paso 4: noUncheckedIndexedAccess → errores nuevos, pero son exactamente los que quería ver
Paso 5: exactOptionalPropertyTypes → último, requiere entender bien el modelo de datos
Una estrategia útil para proyectos grandes es activar los flags con // @ts-expect-error de forma temporal y resolverlos archivo por archivo. Otra es usar skipLibCheck: true mientras migrás para no quedar bloqueado por tipos de dependencias que todavía no fueron actualizadas.
// tsconfig.json — configuración de migración progresiva
{
"compilerOptions": {
// Paso 1: empezá por acá
"strictNullChecks": true,
// Paso 2: una vez que el proyecto compila con el anterior
"noImplicitAny": true,
// Paso 3: activa el grupo base completo
"strict": true,
// Paso 4 y 5: después de estabilizar el grupo base
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
// Temporal durante la migración:
"skipLibCheck": true
}
}
Los errores que más se cometen al migrar
Activar todo de una y abandonar. El CI explota con 400 errores y alguien decide que "TypeScript strict es demasiado restrictivo". El problema no es el flag — es el orden.
Usar as para silenciar en lugar de corregir. Cada as unknown as TipoQueQuiero es una deuda de tipos. Patea el error a runtime y hace que la migración sea cosmética.
// Esto no es una migración, es un disfraz:
const result = fetchUser() as User; // ❌ Ignora que fetchUser puede retornar null
// Esto sí:
const raw = await fetchUser();
if (!raw) throw new Error("Usuario no encontrado");
const result: User = raw; // ✅
Ignorar los dos flags fuera de strict. Este es el error más común y el que motivó este post. Muchos equipos declaran TypeScript estricto sin saber que noUncheckedIndexedAccess no está incluido en ese preset.
Activar exactOptionalPropertyTypes sin revisar los updates de Prisma. En Prisma, los updates usan propiedades opcionales extensamente. Con este flag, hay patrones que antes compilaban y dejan de hacerlo. No es un bloqueo — es una señal de que había un modelo de datos impreciso. Pero conviene saber que el lote de errores va a aparecer ahí.
Qué no podés concluir solo con esto
Este análisis se basa en la documentación oficial y en patrones conocidos de TypeScript. Lo que no podés inferir de acá:
- Cuántos errores va a generar en tu codebase específica. Eso solo lo sabés corriendo
tsc --noEmitcon cada flag activado. - Si
exactOptionalPropertyTypesvale el costo en un proyecto con Prisma v5 sin refactors previos. Puede ser mucho trabajo por valor marginal si el modelo de datos está bien tipado de otra forma. - Si hay incompatibilidades con librerías de terceros que no soportan bien
noUncheckedIndexedAccess.skipLibCheck: truemitiga esto, pero no lo elimina.
La decisión de cuándo activar cada flag requiere correr el compilador en el propio código y leer los errores. No hay atajos acá.
FAQ
¿strict: true activa noUncheckedIndexedAccess?
No. strict: true es un preset que activa ocho flags específicos documentados en la referencia oficial. noUncheckedIndexedAccess no es uno de ellos. Hay que activarlo por separado en el tsconfig.json.
¿Cuál es el primer flag que debería activar si mi proyecto no tiene ninguno?
strictNullChecks. Es el que previene la mayor clase de errores en runtime y es el prerequisito lógico para que el resto de los flags tenga sentido. Sin chequeos de null, los otros flags son decoración.
¿noImplicitAny rompe el uso de any explícito?
No. noImplicitAny solo penaliza el any que TypeScript infiere cuando no puede determinar el tipo. Si escribís any explícito (const x: any = ...), compila igual. Eso es intencional: a veces necesitás escaparte del sistema de tipos. Pero al menos lo hacés conscientemente.
¿Puedo activar estos flags progresivamente en un monorepo?
Sí. Cada paquete del monorepo puede tener su propio tsconfig.json que extiende una base compartida. Una estrategia común es activar los flags más estrictos en los paquetes nuevos y migrar los viejos de forma incremental. El riesgo es que los tipos que cruzan paquetes pueden quedar en zonas grises durante la transición.
¿exactOptionalPropertyTypes rompe los spreads de objetos?
Puede hacerlo si estás usando spreads para pasar propiedades opcionales con valor undefined. El compilador va a marcar esos casos porque hay una diferencia semántica entre propiedad ausente y propiedad con valor undefined. En la mayoría de los casos, el fix es usar narrowing o spreads condicionales en lugar de asumir que undefined pasa transparentemente.
¿Vale la pena activar todo esto en un proyecto que ya funciona?
Depende del costo de los bugs que querés prevenir. Si el sistema maneja autenticación, datos financieros o cualquier tipo de información donde un error silencioso tiene consecuencias reales — sí, vale la pena el costo de la migración. Si es un prototipo interno que no llega a usuarios — quizás strict: true alcanza por ahora. El criterio es el costo del error, no la comodidad del setup. Este tema conecta directamente con decisiones de arquitectura más amplias, del tipo de las que aparecen en el post sobre .
El próximo paso concreto: corré tsc --noEmit con noUncheckedIndexedAccess: true en el proyecto donde estás trabajando ahora. Leé los errores. Si son manejables, activalo. Si son 200+ errores, empezá por los archivos más críticos. No necesitás resolver todo de una — necesitás saber qué ignorabas.
Fuentes originales:
- TypeScript Strict Mode Docs:
Este artículo fue publicado originalmente en juanchi.dev
SOCIAL SHARE CARD GENERATOR