Mucha gente pone "strict": true en el tsconfig.json porque lo ha visto en una plantilla o en un tutorial, pero sin saber exactamente qué está activando. No es una opción mágica: es un shorthand que enciende ocho flags de golpe.
Estas son las ocho, con su nombre exacto:
- strictNullChecks:
nullyundefineddejan de ser valores válidos para cualquier tipo. Solo son de sí mismos. - noImplicitAny: si TypeScript no puede inferir el tipo de algo, da error en vez de poner
anysilenciosamente. - strictFunctionTypes: aplica covarianza y contravarianza correctas en los tipos de función.
- strictBindCallApply:
bind,callyapplyse verifican con los tipos reales del método. - strictPropertyInitialization: las propiedades de una clase tienen que inicializarse en el constructor.
- noImplicitThis: usar
thissin tipo explícito da error. - alwaysStrict: emite
"use strict"en todos los archivos generados. - useUnknownInCatchVariables: el parámetro
ede un bloquecatchesunknownen vez deany.
Si activas strict, activas las ocho. Si solo quieres algunas, puedes listarlas una a una y omitir strict. Lo habitual es usar el shorthand y añadir las flags extra que no cubre.
strictNullChecks: el más importante de los ocho
Sin strictNullChecks, TypeScript permite esto sin rechistar:
const nombre: string = null;
nombre.toUpperCase(); // explota en runtime, TS no dice nada
Con él activado, esa asignación ya da error en tiempo de compilación. Para trabajar con valores que pueden ser null o undefined tienes varias opciones:
- Optional chaining:
usuario?.nombre - Comprobación explícita:
if (usuario !== null) { ... } - Valor por defecto con nullish coalescing:
usuario?.nombre ?? 'Anónimo'
También existe el operador !, el llamado non-null assertion:
usuario!.nombre // "confío en que no es null, TS, no te preocupes"
Úsalo con cabeza. Es una promesa que le haces al compilador, y si mientes, el error pasa de compile time a runtime, que es exactamente lo que querías evitar.
noImplicitAny: no más tipos fantasma
TypeScript es muy listo infiriendo tipos, pero hay situaciones donde no puede. Sin noImplicitAny, en esos casos pone any sin avisarte. Con la flag activada, da error y te obliga a ser explícito.
Los casos más típicos:
// Parámetro sin tipo ? error con noImplicitAny
function suma(a, b) {
return a + b;
}
// Correcto
function suma(a: number, b: number): number {
return a + b;
}
¿Por qué importa tanto? Porque any desactiva el type checker para esa variable. Es como escribir JavaScript de los años 2000, pero con la falsa sensación de que tienes tipos. Si ves any en un proyecto TypeScript, es una señal de alarma, no de flexibilidad.
strictPropertyInitialization: las clases tienen que ordenarse
Con strict activado, si declaras una propiedad en una clase, TypeScript exige que la asignes en el constructor:
// Error: Property 'nombre' has no initializer
class Usuario {
nombre: string;
}
// Opciones válidas:
class Usuario {
nombre: string = ''; // valor por defecto
constructor(nombre: string) {
this.nombre = nombre; // asignación en constructor
}
}
Si usas Angular o un framework que inicializa propiedades fuera del constructor (decoradores, inyección de dependencias), verás mucho el patrón con !:
@Input() titulo!: string;
El ! le dice a TypeScript que confías en que esa propiedad tendrá valor antes de usarse. En Angular tiene sentido porque el framework se encarga. En código tuyo propio, piénsatelo dos veces.
Migrar un proyecto existente a strict
Activar "strict": true de golpe en un proyecto grande es como abrir una caja de Pandora: cientos de errores, la mayoría del mismo tipo, y la tentación de tirar la toalla.
El proceso que funciona es gradual:
- Paso 1: activa solo
"noImplicitAny": true. Corrige los errores que aparecen. Son los más mecánicos y los que más valor aportan. - Paso 2: añade
"strictNullChecks": true. Aquí sí que aparecen errores útiles, los que luego serían bugs en producción. - Paso 3: activa el resto de flags del bundle de strict una a una hasta que puedas poner
"strict": truelimpio.
Para proyectos muy grandes, Airbnb publicó ts-migrate, una herramienta que automatiza parte de la migración añadiendo // @ts-expect-error donde no puede inferir el tipo. No es ideal, pero te permite ir a strict sin bloquear el proyecto semanas enteras.
any vs unknown: cuál usar y cuándo
Con useUnknownInCatchVariables, el parámetro de un bloque catch pasa de ser any a ser unknown. La diferencia importa:
try {
// ...
} catch (e) {
console.log(e.message); // Error con unknown: no puedes acceder sin comprobar antes
}
// Correcto:
try {
// ...
} catch (e) {
if (e instanceof Error) {
console.log(e.message);
}
}
Tiene toda la lógica. Un catch puede recibir cualquier cosa: un objeto Error, un string, un número, lo que sea. Asumir que siempre es un Error con .message es un bug esperando a ocurrir.
La diferencia entre any y unknown en general:
any: acepta todo y lo puedes usar en cualquier sitio sin restricciones. El type checker se rinde.unknown: acepta cualquier valor, pero para usarlo tienes que comprobar primero qué tipo es. El type checker sigue activo.
Cuando necesites un tipo "cajón de sastre", usa unknown y no any. Es más trabajo, pero es el trabajo que tiene sentido.
Flags que vale la pena añadir además de strict
strict es el punto de partida, no el techo. Hay cuatro flags fuera del bundle que merece la pena conocer:
- noUncheckedIndexedAccess:
array[0]devuelveT | undefineden vez deT. El acceso a posiciones de un array puede devolverundefinedsi el índice se pasa de rango, y TypeScript por defecto no lo tiene en cuenta. - exactOptionalPropertyTypes:
{ x?: string }no acepta{ x: undefined }. Si una propiedad es opcional, realmente es que puede no estar, no que pueda serundefinedexplícito. - noImplicitReturns: todas las ramas de una función que devuelve algo tienen que devolver un valor. Evita el clásico bug de la función que en algunos casos no llega al
return. - noFallthroughCasesInSwitch: cada
casede unswitchtiene que tenerbreak,returnothrow. Nada de dejar que el flujo caiga al siguiente caso por error.
De estos cuatro, noUncheckedIndexedAccess es el que más errores inesperados destapa en proyectos existentes. Actívalo en proyectos nuevos desde el principio.
tsconfig recomendado para proyectos nuevos en 2026
Si arrancas un proyecto TypeScript desde cero hoy, este es un punto de partida razonable:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "bundler",
"module": "ESNext",
"target": "ESNext",
"esModuleInterop": true,
"skipLibCheck": true
}
}
moduleResolution: "bundler" es el valor correcto si usas Vite, esbuild o Bun. El antiguo "node" ya no encaja bien con proyectos modernos. Con Next.js o Node puro, usa "node16" o "nodenext".
Para profundizar en la configuración general de TypeScript y las herramientas que han cambiado el flujo de trabajo en los últimos años, puedes ver TypeScript en 2025: configuración y herramientas. Y si quieres recordar de dónde viene todo esto, JavaScript sin tipos: el punto de partida lo pone en contexto.
Imagen: Pexels / Nataliya Vaitkevich
