SDK Recipes: SPA & Performance

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 slots
  • dlApi.changeView() - Update targeting context
  • dlApi.defineSlot() - Create ad slot
  • dlApi.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 loads
  • data-prop-lazy - Per-slot lazy loading control
  • data-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
  • slot with keyvalues property - 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