Understanding CLS Issues in AdSense Ads
Many website owners and bloggers using Google AdSense experience a high Cumulative Layout Shift (CLS) score, negatively impacting their site's performance and user experience. This issue occurs when ads are rendered dynamically without reserving the necessary space beforehand, causing content to unexpectedly shift, frustrating users and affecting rankings in Google's Core Web Vitals.
Why CLS Happens and How to Prevent It
When AdSense loads an ad, its script expands the space dynamically, pushing surrounding content downward—resulting in a disruptive layout shift. The solution? Pre-reserving space for the ad before it loads to prevent any unwanted movement. However, pre-reserving space raises a concern: what if no ad loads? This would leave an empty block of space, making your website look awkward and unfinished.
Best Approach: Using Ad Placeholders
Instead of leaving blank space when no ad loads, you can fill it with a visually appealing ad placeholder—such as:
- A custom banner promoting your own product or service.
- A placeholder graphic ensuring the layout remains consistent.
- A message or CTA (Call-To-Action) relevant to your audience.
This method ensures the user experience remains smooth while preventing layout shifts from unfilled ad slots.
Creating Ad Placeholders with HTML & CSS
The first step is to define the size of your ad placeholder, matching Google AdSense’s standard ad dimensions:
- 728x90
- 336x280
- 300x250
- 300x50
- 160x600
Use this link to access the Google support page for more information.
Example Code for a Simple Ad Placeholder
Here’s a basic CSS and HTML template for a 300×250 ad placeholder:
.placeholder{
border: solid 0px transparent;
border-radius: 5px;
width: 300px;
height: 250px;
background: rgb(84,217,213);
background: linear-gradient(0deg, rgba(84,217,213,1) 0%, rgba(60,147,207,0.6559873949579832) 100%);
-webkit-box-shadow: 0px 0px 5px 0px rgba(67,66,69,1);
-moz-box-shadow: 0px 0px 5px 0px rgba(67,66,69,1);
box-shadow: 0px 0px 5px 0px rgba(67,66,69,1);
}
.placeholder span{
line-height:230px;
text-align: center;
display: block;
font-weight: bold;
font-size: 25px;
color: white;
position: relative;
letter-spacing: 0.02em;
text-transform: uppercase;
text-shadow: 0.01em 0 0.15em rgba(128,128,128,1);
user-select: none;
white-space: nowrap;
filter: blur(0.007em);
}
<div class="placeholder">
<span>Ad Placeholder</span>
</div>
The finished placeholder:

This placeholder maintains design consistency while waiting for an ad to be displayed.
Detecting Whether an Ad Has Loaded
To determine if an ad has successfully loaded or not, Google AdSense uses the data-ad-status attribute inside the ins tag:
data-ad-status = "filled" //An ad is loaded
data-ad-status = "unfilled" //No ad is loaded
Using JavaScript's MutationObserver, you can monitor changes to this attribute and automatically swap the placeholder when needed.
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-xxxxxxxxxx crossorigin="anonymous"></script>
<ins class="adsbygoogle"
style="display:inline-block;width:300px;height:250px"
data-ad-client="ca-pub-xxxxxxxxxx"
data-ad-slot="1234567890"></ins>
<script>
(adsbygoogle = window.adsbygoogle || []).push({});
</script>
JavaScript Code for Detecting Ads
//in case we have multiple ads to check we will select all ins with the same class
var ins_elements = document.querySelectorAll(".ad-slot-1");
//the callback function which will called when a mutation is occurring
var handleMutation = function(mutations, observer){
for (var i = 0; i < mutations.length; i++){
//check if mutation is on data-ad-status
if ( mutations[i].attributeName === "data-ad-status" ) {
//check if in is filled or not
if ( mutations[i].target.dataset.adStatus === "filled" ) {
//get the placeholder and remove it
var place_holder = mutations[i].target.querySelector(".placeholder");
if ( place_holder !== null ) {
place_holder.remove();
}
//disconnect the observer
observer.diconnect();
} else {
//add the placeholder if necessary
if ( mutations[i].target.querySelector(".placeholder") === null ) {
mutations[i].target.insertAdjacentHTML( 'beforeend', '<div class="placeholder"><span>Ad Placeholder</span></div>');
}
}
}
}
};
const observer = new MutationObserver(handleMutation);
const config = { attributes: true, childList: true, subtree: true };
//looping through all selected ads
for( var i = 0; i < ins_elements.length; i++ ) {
observer.observe(ins_elements[i], config);
}
The code you need for a solution to the issue
Bellow you will find the code above stitched up in a sing HTML document, this is just one approach, there are many other ways fix the using the same concept.
<html>
<head>
<title></title>
<style>
.ad-slot-1{
width: 300px;
height: 250px;
display: block;
border: solid 1px black;
cursor: pointer;
position: relative;
z-index: 99999;
}
.placeholder{
border: solid 0px transparent;
border-radius: 5px;
width: 300px;
height: 250px;
background: rgb(84,217,213);
background: linear-gradient(0deg, rgba(84,217,213,1) 0%, rgba(60,147,207,0.6559873949579832) 100%);
-webkit-box-shadow: 0px 0px 5px 0px rgba(67,66,69,1);
-moz-box-shadow: 0px 0px 5px 0px rgba(67,66,69,1);
box-shadow: 0px 0px 5px 0px rgba(67,66,69,1);
text-decoration: none;
position: relative;
z-index: 1;
}
.placeholder span{
line-height:230px;
text-align: center;
display: block;
font-weight: bold;
font-size: 25px;
color: white;
position: relative;
letter-spacing: 0.02em;
text-transform: uppercase;
text-shadow: 0.01em 0 0.15em rgba(128,128,128,1);
user-select: none;
white-space: nowrap;
filter: blur(0.007em);
}
</style>
</head>
<body>
<ins id="ad-ins-1" class="ad-slot-1" data-ad-status=""></ins>
<script>
//in case we have multiple ads to check we will select all ins with the same class
var ins_elements = document.querySelectorAll(".ad-slot-1");
//the callback function which will called when a mutation is occurring
var handleMutation = function(mutations, observer){
for (var i = 0; i < mutations.length; i++){
console.log("mutation");
//check if mutation is on data-ad-status
if ( mutations[i].attributeName === "data-ad-status" ) {
//check if in is filled or not
if ( mutations[i].target.dataset.adStatus === "filled" ) {
//get the placeholder and remove it
var place_holder = mutations[i].target.querySelector(".placeholder");
if ( place_holder !== null ) {
place_holder.remove();
}
//disconnect the observer
observer.diconnect();
} else {
console.log("unfilled");
//add the placeholder if necessary
if ( mutations[i].target.querySelector(".placeholder") === null ) {
mutations[i].target.insertAdjacentHTML( 'beforeend', '<div class="placeholder"><span>Ad Placeholder</span></div>');
}
}
}
}
};
const observer = new MutationObserver(handleMutation);
const config = { attributes: true, childList: true, subtree: true };
//looping through all selected ads
for( var i = 0; i < ins_elements.length; i++ ) {
observer.observe(ins_elements[i], config);
}
</script>
</body>
</html>
This script ensures smooth transitions, eliminating empty spaces when ads do not load while maintaining SEO performance and user experience
Fixing CLS issues in Google AdSense ads is essential for boosting Core Web Vitals scores and enhancing the user experience. By pre-reserving ad space and strategically implementing ad placeholders, you maintain a seamless layout—preventing unexpected shifts that could harm engagement and rankings.
Comments (0)