Strict null checks en TypeScript: lo que el compilador no te dice y dónde sí duele en producción
Estaba revisando un Server Action en Next.js — algo que compilaba sin un solo error, tipos limpios, lint verde — cuando llegó un Cannot read properties of undefined (reading 'id') en runtime. Tres minutos de retrospectiva después entendí el problema: el compilador me había dado luz verde y yo lo creí. Eso fue un error.
Mi tesis, sin rodeos: strict null checks es necesario pero insuficiente. El compilador de TypeScript es el primer filtro del sistema, no el último. La verdadera seguridad contra nulls viene de validación en runtime en los bordes del sistema — y hay cuatro patrones concretos donde el compilador dice OK y producción dice otra cosa.
No es un post de "activá strict: true y listo". Es un mapa de dónde el compilador falla en silencio, con el stack Next.js 16 + Prisma ORM 5 + TypeScript estricto como referencia concreta.
Strict null checks en TypeScript producción: qué activa la flag y qué no
Cuando habilitás strict: true en el tsconfig.json, TypeScript activa un conjunto de checks más restrictivos. Según la es la herramienta que mejor encaja en este stack:
// ✅ Validación con Zod en el punto de entrada del dato externo
import { z } from "zod";
const ConfigSchema = z.object({
timeout: z.number().positive(),
endpoint: z.string().url(),
});
async function obtenerConfiguracion() {
const raw = await fs.readFile("config.json", "utf-8");
const parsed = JSON.parse(raw);
return ConfigSchema.parse(parsed); // lanza ZodError si el shape no coincide
}
// Ahora el tipo inferido es exactamente { timeout: number; endpoint: string }
// y el runtime garantiza la forma antes de que el dato llegue al resto del código
const config = await obtenerConfiguracion();
const ms = config.timeout * 1000; // seguro
El mismo patrón aplica a Server Actions en Next.js que reciben datos de formularios, a responses de APIs externas y a cualquier dato que cruce el borde del sistema.
Errores comunes al configurar strict null checks
Hay tres errores que aparecen seguido cuando equipos habilitan strict en un codebase existente:
1. Apagar checks individuales para que compile
// ❌ Esto anula el propósito de strict
{
"compilerOptions": {
"strict": true,
"strictNullChecks": false
}
}
Si un check rompe demasiado código existente, el camino correcto es migrar progresivamente con // @ts-expect-error anotado y fechado — no desactivar la flag globalmente.
2. Usar non-null assertion operator (!) sin guarda real
// ❌ El operador ! le dice al compilador "confiá en mí"
// pero no hace ninguna verificación en runtime
const nombre = usuario!.nombre; // TypeError si usuario es null
Cada ! en el codebase es una deuda técnica potencial. Si ves más de cinco ! en un archivo, es una señal de que los tipos no están modelando bien la realidad del dominio.
3. Confundir que strict en Next.js config y en tsconfig son cosas distintas
next.config.js tiene una opción typescript.ignoreBuildErrors que, si está en true, bypassea completamente el compilador en el build. El strict del tsconfig.json no sirve de nada si el build nunca falla por errores de tipos.
Checklist de decisión: dónde validar y dónde confiar en el compilador
Antes de decidir si agregar validación de runtime o confiar en el tipo estático, pasá por esta checklist:
| Pregunta | Sí | No |
|---|---|---|
| ¿El dato viene de fuera del proceso? (API, archivo, DB, formulario) | Validar con Zod | El compilador alcanza |
¿La librería tiene tipos any o tipos de @types/ desactualizados? | Agregar guarda explícita | El compilador alcanza |
| ¿Usás assertion functions propias? | Verificar que lancen throw | — |
¿La relación de Prisma está en el include? | El tipo es preciso | Agregar guarda defensiva |
¿El tipo usa ! para suprimir un null? | Revisitar el modelo de dominio | — |
Regla de dedo: si el dato cruzó un borde del sistema (red, disco, formulario, variable de entorno), validá en runtime. Si el dato es interno al proceso y el tipo fue inferido por TypeScript, el compilador alcanza.
Límites de esta guía
Lo que no podés concluir de este post sin más evidencia:
- Cuántos bugs en producción vienen de cada patrón — eso depende del codebase específico, la cobertura de tests y la madurez del equipo.
- Si Zod es siempre la mejor opción frente a alternativas como — hay trade-offs de bundle size y ergonomía que merecen análisis propio.
- Si estos patrones aplican igual en un codebase que usa tRPC o GraphQL con codegen — esos sistemas tienen sus propias capas de validación que cambian la ecuación.
Lo que sí podés concluir: los cuatro patrones son reproducibles, tienen solución concreta y aplican directamente al stack Next.js 16 + Prisma 5 + TypeScript estricto.
FAQ — strict null checks TypeScript producción
¿Con strict: true activado puedo confiar en que no hay nulls en runtime?
No. strict: true garantiza que el compilador te avisa cuando un tipo puede ser null o undefined — pero no puede verificar los datos que entran desde afuera del proceso. Los datos de APIs, formularios, archivos y bases de datos necesitan validación en runtime adicional.
¿Prisma ORM genera tipos que reflejan exactamente lo que retorna cada query?
Parcialmente. Prisma 5 infiere el tipo a partir del schema y del include/select de la query. Si no hacés include de una relación, el campo no va a estar en el objeto retornado — pero el tipo generado puede no expresar eso con suficiente precisión en todos los casos. La práctica segura es hacer coincidir siempre el include con lo que el código downstream consume.
¿Cuándo tiene sentido usar // @ts-expect-error en lugar de resolver el tipo correctamente?
Solo en dos casos: cuando estás migrando un codebase legacy a strict de forma progresiva (anotado con un comentario que explique el motivo y una fecha de resolución esperada), o cuando estás testeando un error deliberado. En código de producción estable, @ts-expect-error sin justificación es una deuda técnica con fecha de vencimiento desconocida.
¿JSON.parse siempre retorna any?
Sí, por diseño. TypeScript no puede saber la forma del JSON hasta runtime. La única forma de recuperar un tipo concreto es validar el resultado con una librería como , también tocan la diferencia entre lo que el sistema promete y lo que entrega.
Fuentes originales:
- TypeScript Handbook — Strict Mode:
Este artículo fue publicado originalmente en juanchi.dev
SOCIAL SHARE CARD GENERATOR