Skip to content
Consultation

A Certification Badge With No Image Took Down next/image

Adityo Guni Waluyo

An optional image field met a runtime component that demands src. The About page went blank. Here is the ternary and fallback UI that fixed it.

I added a new certification entry to the per-locale JSON for the About page last Tuesday. It was a regional ISO variant we'd just been accredited for, and no logo file existed yet, so I left the image field out. The build passed, TypeScript stayed quiet. Then I loaded the page in dev and got a blank white screen. The console threw: "Image is missing required \"src\" property" from next/image. The whole About page was gone because the badge component rendered without a check. My first thought was that the OptimizedImage wrapper I wrote around next/image was broken, or that next/image itself had a regression in this Next.js version. I almost rolled back the commit before reading the error again.

The Error Was by Design

I dug into the [next/image API reference](https://nextjs.org/docs/app/api-reference/components/image) and saw src is a required prop. The library validates at runtime and throws that exact message when the value is undefined. The [GitHub issue tracking "Image is missing required src property" when src is undefined](https://github.com/vercel/next.js/issues/29317) confirmed it's expected behavior, not a bug in my code. My wrapper wasn't at fault; next/image is a runtime component that demands a string to render. The page crashed because undefined slipped through the render path. I'd assumed making the field optional in the type was the safe fix. It was only half a fix. The compile step smiled, the browser blanked.

Types Vanish at Runtime

TypeScript's [strictNullChecks](https://www.typescriptlang.org/tsconfig/strictNullChecks.html) gives undefined its own type, so image?: string is honest inside the editor. But after compilation, those types erase completely. At runtime, certification.image is just undefined, and React still tries to render it. The [React conditional rendering docs](https://react.dev/learn/conditional-rendering) show a ternary is the natural guard for exactly this case. The crash didn't happen because I misused types; it happened because I forgot the runtime side of the contract. An optional field in an interface doesn't free you from handling the absence. The compiler won't stop your user from staring at a white screen. There's a second trap hiding in the data path: once this object goes through JSON, an optional property holding undefined disappears entirely, so whatever supplies the data simply omits the key. That is exactly the shape the badge component received, and it is a perfectly legal shape as far as the type is concerned.

The Fallback Is the Contract

The commit made image optional and added a render branch. The fallback is a div with a ShieldCheck icon on bg-primary/10, not a broken image or empty space. Here's the exact JSX:

{certification.image ? (
  <OptimizedImage src={certification.image} alt={certification.name} />
) : (
  <div className="bg-primary/10 flex h-12 w-12 items-center justify-center rounded">
    <ShieldCheck className="h-6 w-6" />
  </div>
)}

I hold a strong opinion: that fallback UI is part of the data contract, not a patch. An honest type without render handling just moves the crash from compile time to runtime. They ship together. The same commit also added a welfare section on the about page and an icon map for new services, where an unknown key falls back to a default icon instead of throwing. Same philosophy in both places: data drives the UI, and a missing entry gets a branch, not a crash. The lesson stuck: optional means you must write the else block.

I now treat every optional field as a promise to branch. The About page stays up, and the cert badges look intentional even without logos. If you make a prop optional, write the missing case first. That's the lazy senior move: avoid the page-killing bug by handling absence upfront.

If you want the testing side of this story, I wrote about bugs that shipped while the verify suite reported zero failures and the Shiki code block contract with short lang IDs.

Related articles