Reserving Space for Ad and Promo Slots

Content sites that fund themselves with advertising or sponsorships have a structural layout shift problem: the most valuable slots are near the top of the page, and their contents arrive last. An ad request goes out after the page scripts run, the auction takes hundreds of milliseconds, and a creative of unpredictable size lands in a slot that had no size until then. The paragraph the reader just started moves down 250 pixels. Promo banners inserted by marketing scripts and "sponsor" blocks loaded from a CMS at runtime do the same thing on sites without ads.

This guide reserves space for ad, sponsor and promo slots on a static developer blog that runs display ads in three slots and a rotating sponsor block, sizes the reservations from fill data, handles unfilled slots, moves promos that are known in advance into the build, and measures field CLS before and after. It is part of Cumulative Layout Shift Fixes for Static Sites.

Prerequisites

  • An ad server or sponsor source that reports creative sizes per slot (Google Ad Manager, a sponsorship CMS, or a simple JSON of sponsors).
  • Field CLS with attribution, as in Debugging CLS with the Layout Instability API.
  • Control over the slot containers in the site's templates.

Where the Shifts Came From

Field attribution over two weeks on article pages, mobile:

Largest shift targetShare of poor-CLS views
In-content ad slot after first paragraph46%
Top leaderboard slot27%
Sponsor block in sidebar (collapses to top on mobile)14%
Promo banner inserted by marketing script9%
Other4%

Field CLS at p75 on mobile article pages was 0.27 — well into "poor" — while Lighthouse, which often receives no ad fill in lab conditions, reported 0.02.

Share of poor-CLS page views by largest shift target Horizontal bars. The in-content ad slot after the first paragraph caused 46 percent of poor-CLS views. The top leaderboard 27 percent. The sponsor block 14 percent. A promo banner inserted by a marketing script 9 percent. Other causes 4 percent. What caused poor CLS on article pages In-content ad slot 46% Top leaderboard 27% Sponsor block 14% Promo banner script 9% Other 4%
Two weeks of web-vitals attribution on mobile article pages with CLS above 0.1.

Step 1: Size Each Slot From Fill Data

Reserve the space the slot most often needs, per breakpoint. The ad server's delivery report gave the distribution of served creative sizes per slot:

SlotMobile sizes servedDesktop sizes served
Top leaderboard320×50 (61%), 320×100 (39%)728×90 (82%), 970×90 (18%)
In-content300×250 (93%), 336×280 (7%)300×250 (71%), 336×280 (29%)

Reservations: top leaderboard 100 px on mobile, 90 px on desktop; in-content 280 px on both. Then restrict each slot's allowed sizes in the ad server so nothing larger than the reservation can serve — the 336×280 creatives fit the 280 px reservation, while an occasional 300×600 "half page" that had been allowed in the in-content slot was removed from it.

.ad-slot { display: grid; place-items: center; overflow: hidden; background: var(--surface-muted); }
.ad-slot--top { min-height: 100px; }
.ad-slot--inline { min-height: 280px; margin-block: 1.5rem; }
@media (min-width: 48em) { .ad-slot--top { min-height: 90px; } }
.ad-slot::before { content: 'Advertisement'; font-size: .75rem; color: var(--text-muted); grid-area: 1 / 1; align-self: start; }

place-items: center centres smaller creatives in the reserved space instead of letting the slot shrink. The label satisfies ad disclosure and makes the empty space look intentional while the ad loads.

Layout behaviour of an ad slot with and without reserved space Two panels. Without reservation, the slot is zero height in the HTML, the ad arrives about 1.2 seconds after first paint and pushes the article down 280 pixels, and an unfilled slot stays empty but other elements already moved. With reservation, the slot is 280 pixels from the first frame, the ad fills the centre, and an unfilled slot shows a house promo in the same space. An unreserved slot versus a reserved slot No reservation 0 px in static HTML ad lands ~1.2 s after paint article pushed down 280 px CLS 0.12 from one slot Reserved slot 280 px from the first frame creative centred in the box unfilled: house promo, same size CLS 0 from the slot
The reservation costs a gray box for about a second; the alternative costs the reader their place in the text.

Step 2: Never Collapse an Unfilled Slot

About 11% of in-content slot requests did not fill. The previous implementation collapsed empty slots to zero height to avoid a blank box, which shifted content upward — layout shift counts movement in any direction. Now an unfilled slot shows a house promo sized to the reservation: a link to the newsletter or a popular guide, rendered from a small set of static images at build time. In Google Ad Manager, googletag.pubads().collapseEmptyDivs() must not be called for above-the-fold slots; the slotRenderEnded event with isEmpty: true is where the house promo is inserted into the already-reserved space.

Step 3: Load Slots Below the Fold Lazily, Not Late

Lazy-loading ads further down the page is good for performance, but the reservation must exist from the first paint regardless of when the ad loads. Lazy loading with googletag.pubads().enableLazyLoad({ fetchMarginPercent: 200, renderMarginPercent: 100 }) fetches ads as the reader approaches them; since the slots are already sized, the ad arriving while the reader scrolls towards it moves nothing. Scroll-time shifts are counted in CLS, so reserved space matters just as much for slots below the fold.

Step 4: Move Promos and Sponsors Into the Build

The sponsor block and the promo banner were both inserted at runtime: the sponsor from a small API that rotated sponsors per request, and the banner from a marketing tool's script. Both were known in advance — the sponsor schedule is set weekly and campaigns are planned — so both moved into the build:

  • Sponsors live in a data file with start and end dates. The build renders the current sponsor into the HTML; a scheduled daily rebuild rotates it, as in Scheduling Content Publication with Cron-Triggered Builds.
  • Promo banners are a frontmatter field on the site config with dates and target sections; the layout renders them statically.

This removed the marketing tool's script entirely, which also helped INP, and made both blocks part of the first frame.

Moving sponsor and promo blocks from runtime insertion to the build A sponsor schedule and campaign calendar are stored as data files with dates. A daily scheduled build selects the active sponsor and banner and renders them into the static HTML. Readers receive pages where both blocks occupy their space from the first frame. No runtime script inserts content above the fold. Promos rendered at build time Sponsor + campaign data dates in YAML Daily scheduled build cron, 06:00 UTC Select active items by date and section Static HTML blocks in first frame No runtime insert marketing script removed
The daily rebuild takes 48 seconds on this blog, which is more than enough precision for weekly sponsorships.

Working With the Ad Operations Team

Most of this work touches settings that belong to whoever manages the ad server, not to the developers. The change that made it stick was a short shared document listing each slot, its reserved size per breakpoint, and the creative sizes allowed in it, with the reason: "in-content slot reserves 280 px; do not allow 300×600 here". When a new campaign needed a larger format, the ad operations team now asked for a new slot with its own reservation rather than enabling the size in an existing one. The field CLS dashboard, grouped by largest shift target, was shared with them too; when an ad-related target reappeared in the top three one month, it traced back to a new sponsor format that had been added without a reservation, and was fixed within a day.

Measured Impact

Field data from web-vitals RUM on mobile article pages, two weeks before and after:

MeasureBeforeAfter
CLS p75, mobile articles0.270.04
CLS p75, desktop articles0.140.02
Page views with CLS > 0.2529%2%
Ad viewability (ad server report)58%64%
Revenue per thousand page viewsbaseline+3%
Third-party scripts on article pages76

Viewability improved slightly because ads no longer appeared after the reader had already scrolled past the point where they would land; publishers often fear that reserving space hurts revenue, and on this site it did the opposite.

Pitfalls & Rollback

  • Collapsing empty slots. Upward movement counts as shift; fill with a house promo instead.
  • Allowing sizes larger than the reservation. Restrict slot sizes in the ad server to match.
  • Reserving only for above-the-fold slots. Scroll-time shifts are counted too.
  • Runtime personalization above the fold. If a block must be personalized, reserve its space and swap contents without changing size.
  • Rollback: reservations are CSS; removing the min-height rules restores the previous behaviour, and the build-time promos can be switched back to runtime scripts if needed.

Conclusion

Ads and promos shift static pages because they arrive after the HTML with sizes the layout did not expect. Reserve each slot's most common size per breakpoint from fill data, restrict slots to sizes that fit, fill empty slots rather than collapsing them, and render anything known in advance at build time. On this blog that took mobile CLS from 0.27 to 0.04 and slightly improved ad performance.

FAQ

How do I reserve space for an ad whose size is not known in advance?

Reserve the most likely size for each breakpoint with min-height on the slot container, based on historical fill data. When a smaller ad fills the slot, centre it in the reserved space; when a larger one would fill it, restrict the allowed sizes for that slot so it cannot exceed the reservation.

What should happen when an ad slot does not fill?

Keep the reserved space and show a house promo or a neutral placeholder, rather than collapsing the slot. Collapsing an unfilled slot above the fold shifts everything below it upward, which counts as layout shift just like expansion.

Are sticky ads a CLS problem?

Sticky or anchored ads that overlay content at the edge of the viewport do not shift other elements and are not counted as layout shift for the page content, but they must not push content when they appear, and they cover content, which has its own usability and policy implications.

Why render promo banners at build time?

A promotion known when the site is built can be written into the HTML, so it occupies its space from the first frame. Inserting it with JavaScript after load, or from a third-party personalization script, is one of the most common causes of layout shift on static sites.

Filed underHow-tos