Make nullability explicit in types and handle it precisely instead of papering over it with assumptions.
undefined or null at runtime (e.g. c.req.param('id'), a ref’s initial value, a route path when no match exists), the type and tests must say so instead of claiming a non-nullable type. Add overloads when behavior differs based on whether an argument/initial value is provided, e.g.:
export function useRef<T>(initialValue: T): RefObject<T>
export function useRef<T>(initialValue: T | null): RefObject<T | null>
export function useRef<T = undefined>(): RefObject<T | undefined>
?? or ||. ?? only catches null/undefined; it will not catch an intentionally-empty string, 0, or false. When an empty value (like an empty string) has special meaning, check for it explicitly rather than relying on ??:
// `??` won't treat '' as "missing" - check explicitly when empty string matters
const headerName = options?.headerName ?? 'X-Request-Id'
let requestId = headerName ? c.req.header(headerName) : undefined
undefined is a no-op, so ...(args?.header ?? {}) can simply be ...args?.header), and don’t re-check a condition already guaranteed by an earlier check or type narrowing. Conversely, don’t rely on non-null assertions (!) to silence the compiler when an explicit check is possible—prefer narrowing over assertion:
```ts
// Prefer this
const contentType = headers.get(‘Content-Type’)
if (contentType !== null && contentType.startsWith(‘multipart/form-data’)) { … }// over this const contentType = headers.get(‘Content-Type’)! ```
When a field’s optionality in third-party/runtime data is inconsistent across versions or environments (e.g. AWS Lambda event headers, multiValueHeaders, API Gateway v1 vs v2 payloads), model it as optional in the type and guard each access independently; don’t assume one field’s presence implies another’s, and don’t use else if between independently-optional fields.
For “not found”/absent results, pick one consistent representation (e.g. returning '' instead of undefined to preserve a non-nullable return type) and document it, rather than silently changing a function’s return type or leaving the absent case undefined-but-untyped. Likewise, avoid introducing bugs where undefined is treated as “no cached value yet” versus a legitimately resolved undefined result—use a sentinel object/wrapper instead of relying on undefined as both “missing” and “empty” states.
Applying these rules prevents type-level lies, silent runtime bugs from falsy/nullish mixups, and either unsafe assertions or defensive-but-redundant null checks.