From f6dbe4c44c2a5dae69389924b3798ed3fb509c8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Kadir=20Ak=C4=B1n?= Date: Wed, 18 Mar 2026 18:19:42 +0300 Subject: [PATCH] fix(ads): Fix logic for checking ad content presence in Ezoic components --- src/components/ads/ezoic-placeholder.tsx | 37 ++++++++++++++++++++++-- src/components/layout/ezoic-ads.tsx | 11 +++++-- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/components/ads/ezoic-placeholder.tsx b/src/components/ads/ezoic-placeholder.tsx index 1c9487e3..d4bb0eb8 100644 --- a/src/components/ads/ezoic-placeholder.tsx +++ b/src/components/ads/ezoic-placeholder.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useTranslations } from "next-intl"; import { runEzoic } from "@/lib/ezoic"; @@ -11,6 +11,8 @@ interface EzoicPlaceholderProps { export function EzoicPlaceholder({ id }: EzoicPlaceholderProps) { const t = useTranslations("common"); const [isRendered, setIsRendered] = useState(false); + const [hasAdContent, setHasAdContent] = useState(true); + const placeholderRef = useRef(null); useEffect(() => { // Render the placeholder div first, then tell Ezoic to fill it. @@ -25,14 +27,45 @@ export function EzoicPlaceholder({ id }: EzoicPlaceholderProps) { }); }); + // Observe the placeholder for ad content injection by Ezoic. + // If nothing is injected after a timeout, hide the container + // to avoid showing an empty white card. + const el = document.getElementById(`ezoic-pub-ad-placeholder-${id}`); + let observer: MutationObserver | undefined; + let timeoutId: ReturnType | undefined; + + if (el) { + let filled = false; + + observer = new MutationObserver(() => { + if (el.childNodes.length > 0 || el.innerHTML.trim().length > 0) { + filled = true; + setHasAdContent(true); + } + }); + + observer.observe(el, { childList: true, subtree: true }); + + // If Ezoic doesn't fill the placeholder within 5s, hide it + timeoutId = setTimeout(() => { + if (!filled && el.childNodes.length === 0) { + setHasAdContent(false); + } + }, 5000); + } + return () => { cancelAnimationFrame(rafId); + observer?.disconnect(); + if (timeoutId) clearTimeout(timeoutId); runEzoic(() => { window.ezstandalone?.destroyPlaceholders(id); }); }; }, [id]); + if (!hasAdContent) return null; + return (
@@ -41,7 +74,7 @@ export function EzoicPlaceholder({ id }: EzoicPlaceholderProps) {
- {isRendered &&
} + {isRendered &&
}
); diff --git a/src/components/layout/ezoic-ads.tsx b/src/components/layout/ezoic-ads.tsx index bcece825..b0f26c35 100644 --- a/src/components/layout/ezoic-ads.tsx +++ b/src/components/layout/ezoic-ads.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect } from "react"; +import { useEffect, useRef } from "react"; import Script from "next/script"; import { usePathname } from "next/navigation"; import { runEzoic } from "@/lib/ezoic"; @@ -18,10 +18,17 @@ declare global { export function EzoicAds() { const pathname = usePathname(); + const isFirstRender = useRef(true); // Re-trigger ads on SPA route changes - // Per Ezoic docs: destroyPlaceholders first, then showAds in next frame + // Per Ezoic docs: destroyPlaceholders first, then showAds in next frame. + // Skip the first render to avoid racing with individual EzoicPlaceholder + // components that call showAds(id) on mount. useEffect(() => { + if (isFirstRender.current) { + isFirstRender.current = false; + return; + } runEzoic(() => { window.ezstandalone?.destroyPlaceholders(); requestAnimationFrame(() => {