Real-world recipes for React/Vue SPAs, lazy loading, targeting, and GDPR compliance
SDK Recipes: SPA & Performance
Production-ready code examples for single-page applications, performance optimization, custom targeting, and GDPR compliance.
Recipe 4: I have a React/Vue SPA
Problem: Route changes in single-page applications require ad refresh without full page reload.
React Implementation
// React Hook for Ad Management
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';
function useAds(slotName, containerId) {
const location = useLocation();
useEffect(() => {
if (!window.dlApi) {
console.warn('DL API not loaded yet');
return;
}
window.dlApi.cmd.push(function(dlApi) {
// Clean up old ads from previous route
dlApi.destroySlots();
// Update targeting context based on new route
dlApi.changeView({
target: `MYSITE${location.pathname.replace(/\//g, '_')}`
});
// Define new ad slot
dlApi.defineSlot(slotName, containerId);
// Fetch new ads
dlApi.fetch();
});
// Cleanup on component unmount
return () => {
if (window.dlApi) {
window.dlApi.cmd.push(function(dlApi) {
dlApi.destroySlots();
});
}
};
}, [location, slotName, containerId]);
}
// Usage in Component
function ArticlePage() {
useAds('sidebar', 'ad-sidebar');
return (
<div className="article-page">
<article>
<h1>Article Content</h1>
{/* ... */}
</article>
<aside>
<div id="ad-sidebar" />
</aside>
</div>
);
}Vue 3 Composition API
// Vue 3 Composable for Ad Management
import { onMounted, onUnmounted, watch } from 'vue';
import { useRoute } from 'vue-router';
export function useAds(slotName, containerId) {
const route = useRoute();
const loadAd = () => {
if (!window.dlApi) {
console.warn('DL API not loaded yet');
return;
}
window.dlApi.cmd.push(function(dlApi) {
// Clean previous ads
dlApi.destroySlots();
// Update context
dlApi.changeView({
target: `MYSITE${route.path.replace(/\//g, '_')}`
});
// Load new ad
dlApi.defineSlot(slotName, containerId);
dlApi.fetch();
});
};
onMounted(() => {
loadAd();
});
// Reload ads on route change
watch(() => route.path, () => {
loadAd();
});
onUnmounted(() => {
if (window.dlApi) {
window.dlApi.cmd.push(function(dlApi) {
dlApi.destroySlots();
});
}
});
}
// Usage in Component
export default {
setup() {
useAds('sidebar', 'ad-sidebar');
return {};
}
}Vue 2 Options API
// Vue 2 Mixin for Ad Management
export const adsMixin = {
methods: {
loadAds(slotName, containerId) {
if (!window.dlApi) return;
window.dlApi.cmd.push(function(dlApi) {
dlApi.destroySlots();
dlApi.changeView({
target: `MYSITE${this.$route.path.replace(/\//g, '_')}`
});
dlApi.defineSlot(slotName, containerId);
dlApi.fetch();
});
}
},
mounted() {
this.loadAds(this.slotName, this.containerId);
},
watch: {
'$route'() {
this.loadAds(this.slotName, this.containerId);
}
},
beforeDestroy() {
if (window.dlApi) {
window.dlApi.cmd.push(function(dlApi) {
dlApi.destroySlots();
});
}
}
};
// Usage
export default {
mixins: [adsMixin],
data() {
return {
slotName: 'sidebar',
containerId: 'ad-sidebar'
};
}
}Multiple Ads Per Page
// React: Managing multiple ad slots
function ProductPage() {
const location = useLocation();
useEffect(() => {
if (!window.dlApi) return;
window.dlApi.cmd.push(function(dlApi) {
dlApi.destroySlots();
dlApi.changeView({
target: `SHOP${location.pathname.replace(/\//g, '_')}`,
keyvalues: {
category: 'electronics',
page_type: 'product'
}
});
// Define multiple slots before fetching
dlApi.defineSlot('top-banner', 'ad-top');
dlApi.defineSlot('sidebar', 'ad-sidebar');
dlApi.defineSlot('bottom', 'ad-bottom');
// Single fetch for all slots
dlApi.fetch();
});
return () => {
window.dlApi?.cmd.push(dlApi => dlApi.destroySlots());
};
}, [location]);
return (
<div className="product-page">
<div id="ad-top" />
<div className="content">
<main>{/* Product details */}</main>
<aside id="ad-sidebar" />
</div>
<div id="ad-bottom" />
</div>
);
}SDK Objects Used:
dlApi.destroySlots()- Remove all current ad slotsdlApi.changeView()- Update targeting contextdlApi.defineSlot()- Create ad slotdlApi.fetch()- Request ads from server
Recipe 5: I need lazy loading for performance
Problem: Improve Core Web Vitals by loading ads only when they're about to enter viewport.
Global Lazy Loading
// Enable lazy loading for all ads
dlApi = {
target: "MYSITE/HOMEPAGE",
tid: 1746213,
lazy: 1, // Enable lazy loading
lazyPercentage: 150, // Load when 1.5 viewports away
async: 1
};<!-- All slots lazy load by default -->
<div data-slot="top"></div>
<div data-slot="sidebar"></div>
<div data-slot="footer"></div>Selective Lazy Loading
// Lazy load enabled globally, disable for critical slots
dlApi = {
target: "MYSITE/HOMEPAGE",
tid: 1746213,
lazy: 1,
lazyPercentage: 100,
async: 1
};<!-- Critical above-fold ad: load immediately -->
<div data-slot="top" data-prop-lazy="0"></div>
<!-- Below-fold ads: lazy load -->
<div data-slot="sidebar"></div>
<div data-slot="middle"></div>
<div data-slot="footer"></div>Custom Lazy Thresholds
<!-- Load when very close to viewport -->
<div data-slot="sidebar" data-prop-lazy-percentage="50"></div>
<!-- Load early for better UX -->
<div data-slot="footer" data-prop-lazy-percentage="200"></div>Programmatic Lazy Loading
dlApi.cmd.push(function(dlApi) {
// Define slot with lazy loading
var slot = dlApi.defineSlot("bottom-banner", "ad-bottom", {
lazy: 1,
lazyPercentage: 150
});
// Track when ad loads
slot.on("display", function() {
console.log("Ad lazy loaded and displayed");
});
dlApi.fetch();
});Infinite Scroll with Lazy Loading
// Lazy loading works automatically with infinite scroll
var articleCount = 0;
function loadMoreArticles() {
articleCount++;
// Add content
var article = document.createElement('div');
article.className = 'article';
article.innerHTML = '<h2>Article ' + articleCount + '</h2>';
document.querySelector('.feed').appendChild(article);
// Add ad every 3 articles
if (articleCount % 3 === 0) {
var adContainer = document.createElement('div');
adContainer.setAttribute('data-slot', 'infeed');
adContainer.setAttribute('data-prop-pos', articleCount);
adContainer.setAttribute('data-prop-lazy', '1');
adContainer.setAttribute('data-prop-lazy-percentage', '100');
document.querySelector('.feed').appendChild(adContainer);
// Re-initialize autoslot detection
dlApi.cmd.push(function(dlApi) {
dlApi.defineSlot('infeed', adContainer.id || adContainer, {
pos: articleCount,
lazy: 1
});
dlApi.fetch();
});
}
}
// Trigger on scroll
window.addEventListener('scroll', function() {
if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500) {
loadMoreArticles();
}
});Performance Monitoring
dlApi.cmd.push(function(dlApi) {
var slot = dlApi.defineSlot("sidebar", "ad-sidebar", {
lazy: 1,
lazyPercentage: 150
});
var startTime = performance.now();
slot.on("display", function() {
var loadTime = performance.now() - startTime;
console.log("Lazy ad loaded in " + loadTime + "ms");
// Send to analytics
if (window.gtag) {
gtag('event', 'timing_complete', {
name: 'lazy_ad_load',
value: Math.round(loadTime),
event_category: 'Ads'
});
}
});
dlApi.fetch();
});SDK Objects Used:
- Init
.lazy- Enable lazy loading globally - Init
.lazyPercentage- Viewport distance threshold slot.on("display")- Event when ad loadsdata-prop-lazy- Per-slot lazy loading controldata-prop-lazy-percentage- Per-slot threshold
Recipe 6: I need custom targeting
Problem: Serve personalized ads based on user segments, content categories, or business logic.
Basic Key-Value Targeting
// Set targeting at initialization
dlApi = {
target: "SHOP/PRODUCTS",
tid: 1746213,
keyvalues: {
category: "electronics",
price_range: "100-500",
user_type: "premium"
},
async: 1
};Dynamic Targeting
// Add targeting dynamically based on user actions
dlApi.cmd.push(function(dlApi) {
// User added item to cart
dlApi.addKeyValue("cart_value", "high");
dlApi.addKeyValue("intent", "purchase");
// User searched for something
dlApi.addKeyword("laptop");
dlApi.addKeyword("gaming");
// Now fetch with new targeting
dlApi.fetch();
});E-commerce Targeting
// Product detail page
function loadProductAds(product) {
dlApi.cmd.push(function(dlApi) {
// Update context with product data
dlApi.changeView({
target: "SHOP/PRODUCT_DETAIL",
keyvalues: {
product_id: product.id,
category: product.category,
brand: product.brand,
price: product.priceRange,
in_stock: product.inStock ? "yes" : "no",
discount: product.hasDiscount ? "yes" : "no"
}
});
// Add product keywords
dlApi.addKeyword(product.name.toLowerCase().replace(/\s+/g, '+'));
dlApi.addKeyword(product.category);
dlApi.defineSlot("product-sidebar", "ad-sidebar");
dlApi.fetch();
});
}
// Usage
loadProductAds({
id: "PROD123",
name: "Gaming Laptop RTX 4090",
category: "electronics",
brand: "TechBrand",
priceRange: "2000-3000",
inStock: true,
hasDiscount: false
});Per-Slot Targeting
dlApi.cmd.push(function(dlApi) {
// Top banner: general audience
dlApi.defineSlot("top-banner", "ad-top", {
keyvalues: {
position: "top",
type: "banner"
}
});
// Sidebar: premium users only
dlApi.defineSlot("sidebar", "ad-sidebar", {
keyvalues: {
position: "sidebar",
user_segment: "premium"
}
});
// Footer: budget-conscious users
dlApi.defineSlot("footer", "ad-footer", {
keyvalues: {
position: "footer",
user_segment: "budget"
}
});
dlApi.fetch();
});User Segment Targeting
// Determine user segment from analytics
function getUserSegment() {
// Example: check user behavior
var pageViews = localStorage.getItem('pageViews') || 0;
var hasAccount = !!document.querySelector('.user-logged-in');
var cartValue = parseFloat(localStorage.getItem('cartValue')) || 0;
if (hasAccount && cartValue > 500) return 'vip';
if (hasAccount) return 'registered';
if (pageViews > 10) return 'returning';
return 'new';
}
dlApi.cmd.push(function(dlApi) {
var segment = getUserSegment();
dlApi.addKeyValue("user_segment", segment);
dlApi.addKeyValue("session_depth", localStorage.getItem('pageViews'));
dlApi.fetch();
});Content Category Targeting
// Article page targeting
dlApi.cmd.push(function(dlApi) {
// Extract article metadata
var articleCategory = document.querySelector('meta[property="article:section"]')?.content || 'general';
var articleTags = Array.from(document.querySelectorAll('meta[property="article:tag"]'))
.map(tag => tag.content)
.join('+');
dlApi.changeView({
target: "NEWS/ARTICLE",
keyvalues: {
category: articleCategory,
author: document.querySelector('meta[name="author"]')?.content || 'unknown',
published: document.querySelector('meta[property="article:published_time"]')?.content || ''
}
});
if (articleTags) {
dlApi.addKeyword(articleTags);
}
dlApi.fetch();
});Real-Time Targeting Updates
// Update targeting based on user interaction
document.querySelector('.filter-price').addEventListener('change', function(e) {
var priceRange = e.target.value;
dlApi.cmd.push(function(dlApi) {
// Clear old ads
dlApi.destroySlots();
// Update targeting
dlApi.addKeyValue("price_filter", priceRange);
// Reload ads with new targeting
dlApi.defineSlot("product-list", "ad-products");
dlApi.fetch();
});
});A/B Testing Segments
// Assign user to A/B test variant
function getTestVariant() {
var variant = localStorage.getItem('ab_test_variant');
if (!variant) {
variant = Math.random() < 0.5 ? 'control' : 'treatment';
localStorage.setItem('ab_test_variant', variant);
}
return variant;
}
dlApi.cmd.push(function(dlApi) {
dlApi.addKeyValue("ab_test", getTestVariant());
dlApi.fetch();
});SDK Objects Used:
- Init
.addKeyValue()- Add single key-value pair - Init
.addKeyword()- Add search keyword dlApi.changeView()- Update context with new targeting- Init
.keyvalues- Initial key-values object slotwithkeyvaluesproperty - Per-slot targeting
Recipe 7: I need to know if user consents to ads (GDPR)
Problem: Comply with GDPR by checking user consent before showing personalized ads.
Basic Consent Check
dlApi.cmd.push(function(dlApi) {
// Check if user consented to Google as vendor
dlApi.hasVendorConsentByVendorName(
["google"],
function(hasConsent) {
if (hasConsent) {
console.log("User consented: show personalized ads");
dlApi.fetch();
} else {
console.log("No consent: show non-personalized ads");
dlApi.addKeyValue("npa", "1");
dlApi.fetch();
}
}
);
});Multiple Vendor Consent
dlApi.cmd.push(function(dlApi) {
// Check consent for multiple vendors
dlApi.hasVendorConsentByVendorName(
["google", "facebook", "criteo"],
function(hasConsent) {
if (!hasConsent) {
// User didn't consent to all vendors
dlApi.addKeyValue("npa", "1");
dlApi.addKeyValue("limited_ads", "1");
}
dlApi.fetch();
}
);
});Purpose-Based Consent
dlApi.cmd.push(function(dlApi) {
// Get full consent data
dlApi.getConsentsData(function(consentData) {
var hasPersonalizationConsent = consentData.hasConsent('purpose', 3); // Purpose 3: Personalized ads
var hasStorageConsent = consentData.hasConsent('purpose', 1); // Purpose 1: Storage
if (!hasPersonalizationConsent) {
console.log("No personalization consent");
dlApi.addKeyValue("npa", "1");
}
if (!hasStorageConsent) {
console.log("No storage consent - limited tracking");
dlApi.addKeyValue("no_cookies", "1");
}
dlApi.fetch();
});
});GDPR Region Detection
dlApi.cmd.push(function(dlApi) {
// Check if GDPR applies to this user
dlApi.getGdprApplies(function(gdprApplies) {
if (gdprApplies) {
console.log("GDPR applies - checking consent");
dlApi.hasVendorConsentByVendorName(
["google"],
function(hasConsent) {
if (!hasConsent) {
dlApi.addKeyValue("npa", "1");
}
dlApi.fetch();
}
);
} else {
console.log("GDPR does not apply - full personalization");
dlApi.fetch();
}
});
});Complete GDPR-Compliant Flow
function loadGdprCompliantAds(slotName, containerId) {
if (!window.dlApi) {
console.error("DL API not loaded");
return;
}
dlApi.cmd.push(function(dlApi) {
// Step 1: Check if GDPR applies
dlApi.getGdprApplies(function(gdprApplies) {
if (!gdprApplies) {
// Non-GDPR region: load personalized ads
dlApi.defineSlot(slotName, containerId);
dlApi.fetch();
return;
}
// Step 2: GDPR applies - check vendor consent
dlApi.hasVendorConsentByVendorName(
["google"],
function(hasVendorConsent) {
// Step 3: Check purpose consent
dlApi.getConsentsData(function(consentData) {
var hasPersonalizationConsent = consentData.hasConsent('purpose', 3);
var hasStorageConsent = consentData.hasConsent('purpose', 1);
// Determine ad configuration
var adConfig = {};
if (!hasVendorConsent || !hasPersonalizationConsent) {
console.log("Limited consent: non-personalized ads");
adConfig.npa = "1";
}
if (!hasStorageConsent) {
console.log("No storage consent: disable cookies");
adConfig.no_cookies = "1";
}
// Apply consent-based targeting
for (var key in adConfig) {
dlApi.addKeyValue(key, adConfig[key]);
}
// Load ads
dlApi.defineSlot(slotName, containerId);
dlApi.fetch();
});
}
);
});
});
}
// Usage
loadGdprCompliantAds('sidebar', 'ad-sidebar');Consent Change Listener
// React to consent changes (e.g., user updates preferences)
dlApi.cmd.push(function(dlApi) {
// Listen for consent updates
window.__tcfapi('addEventListener', 2, function(tcData, success) {
if (success && tcData.eventStatus === 'useractioncomplete') {
console.log("User updated consent preferences");
// Reload ads with new consent
dlApi.destroySlots();
dlApi.hasVendorConsentByVendorName(
["google"],
function(hasConsent) {
if (!hasConsent) {
dlApi.addKeyValue("npa", "1");
}
dlApi.defineSlot("sidebar", "ad-sidebar");
dlApi.fetch();
}
);
}
});
});Consent Status Display
// Show consent status to user
dlApi.cmd.push(function(dlApi) {
dlApi.getConsentsData(function(consentData) {
var status = {
personalization: consentData.hasConsent('purpose', 3),
storage: consentData.hasConsent('purpose', 1),
measurement: consentData.hasConsent('purpose', 7)
};
// Display to user
document.getElementById('consent-status').innerHTML = `
<ul>
<li>Personalized Ads: ${status.personalization ? 'Enabled' : 'Disabled'}</li>
<li>Cookie Storage: ${status.storage ? 'Enabled' : 'Disabled'}</li>
<li>Ad Measurement: ${status.measurement ? 'Enabled' : 'Disabled'}</li>
</ul>
`;
// Configure ads based on consent
if (!status.personalization) {
dlApi.addKeyValue("npa", "1");
}
dlApi.fetch();
});
});Publisher Consent
// Check publisher-specific consent
dlApi.cmd.push(function(dlApi) {
dlApi.getPublisherConsentsGTM(function(publisherConsents) {
console.log("Publisher consents:", publisherConsents);
if (!publisherConsents.analytics) {
dlApi.addKeyValue("no_analytics", "1");
}
if (!publisherConsents.personalization) {
dlApi.addKeyValue("npa", "1");
}
dlApi.fetch();
});
});Fallback for No Consent
// Show alternative content when no consent
dlApi.cmd.push(function(dlApi) {
var adContainer = document.getElementById('ad-sidebar');
dlApi.hasVendorConsentByVendorName(
["google"],
function(hasConsent) {
if (hasConsent) {
// Show personalized ads
dlApi.defineSlot("sidebar", "ad-sidebar");
dlApi.fetch();
} else {
// Show alternative content
adContainer.innerHTML = `
<div class="consent-message">
<p>We respect your privacy.</p>
<p>To see relevant ads, please <a href="#" id="open-consent">update your consent preferences</a>.</p>
</div>
`;
document.getElementById('open-consent').addEventListener('click', function(e) {
e.preventDefault();
// Trigger CMP consent UI
if (window.__tcfapi) {
window.__tcfapi('displayConsentUi', 2, function() {});
}
});
}
}
);
});SDK Objects Used:
- dlApi
.hasVendorConsentByVendorName()- Check vendor consent - dlApi
.getConsentsData()- Get detailed consent data - dlApi
.getGdprApplies()- Check if GDPR applies - dlApi
.getPublisherConsentsGTM()- Publisher-specific consent - ConsentData
.hasConsent()- Check purpose/vendor consent window.__tcfapi()- TCF 2.0 API for consent management
Next Steps
- Need more recipes? See SDK Recipes: Advanced
- Troubleshooting? Check Troubleshooting Guide
- API Reference? Read DL API SDK Reference
