Our header-bidding wrapper started life as a fairly standard Prebid build. Getting it to work in a vertical feed was mostly a deletion exercise, which was not what anyone expected going in.
What came out
- The size-mapping module. There is one size. A module that negotiates between eleven of them is pure overhead.
- Lazy-load orchestration. A feed already knows what is about to be visible. The wrapper guessing at it was both redundant and worse.
- The consent-string relay. Not because consent does not matter — because it belongs at the edge, where the auction now runs, not in a client module that can be raced.
- Analytics adapters. Four of them, each phoning home independently. Replaced with one event stream out of the edge worker.
Removing those took the client bundle from 148KB to 31KB, which on our median connection is most of a second.
What we rewrote twice
The bid-response cache. Version one cached by slot, which is the desktop assumption — a slot is a place on a page. In a feed, a slot is a position in a stream and it is gone in a second and a half.
// v1: wrong unit. The slot is not stable.
cache.set(slotId, response)
cache.set(eligibilityKey(context), response, { ttl: TURN_MS })
Version two treats the cache key as "what could legitimately play here" rather than "where is here", which survives the viewer scrolling and lets a prefetched response be used one position later.
What we would keep
The bidder adapter interface, unchanged. It is the one part of Prebid that is genuinely well designed, and every bidder already speaks it. Rewriting it would have bought us nothing and cost us every integration we had.
Fork the parts that encode someone else's assumptions. Keep the parts that encode an industry's agreement.



