ImplementationPhase 2: Display Ads

Phase 2: Add Display Ads

Add visual banner/thinking ads for maximum revenue.

Time: 5-10 minutes

IMPORTANT: This phase is ONLY needed if you want display ads (banners, thinking ads, popups, videos). If you only want hyperlink ads, you can skip this phase entirely.

Prerequisites: You MUST complete Phase 1A and Phase 1B before starting this phase. Display ads require the conversation tracking setup from Phase 1B.

Note: If you’re testing, make sure demo mode is enabled for display ads:

// In your conversation initialization (from Phase 1B)
initializeConversation({
  demoMode: true  // Shows demo display ads for testing
});

Set Up Display Ad Trigger

MODIFY your chat component, add state to trigger ad refreshes:

function YourChatComponent() {
  // Your existing hooks (keep as-is)
  const { conversationId } = useEarnLayerClient(); // Already added in Phase 1B
  const [shouldRefetchAd, setShouldRefetchAd] = useState(false);
  
  const handleSendMessage = async (message: string) => {
    // Your existing send message code (keep as-is)
    // ... your existing send message code ...
    const data = await response.json();
    setMessages(prev => [...prev, { role: 'assistant', content: data.response }]);
    
    // ADD: Trigger ad refetch AFTER AI response
    setShouldRefetchAd(true);
    setTimeout(() => setShouldRefetchAd(false), 100);
  };
  
  // Your existing component code (keep as-is)
  // ... rest of component
}

Add Display Ad Component

CREATE a display ad component (or add to existing file):

// CREATE: New display ad component
import { useDisplayAd } from '@earnlayer/sdk/react';
import { useEffect, useRef } from 'react';
 
function DisplayAdComponent({ shouldRefetch }: { shouldRefetch: boolean }) {
  // ADD: EarnLayer display ad hook
  const { ad, isLoading, refetch, refetchWithRefresh } = useDisplayAd({
    adType: 'banner',
    autoFetch: false  // We'll control fetching manually
  });
 
  const prevShouldRefetch = useRef(false);
 
  // ADD: Fetch when shouldRefetch toggles from false to true
  useEffect(() => {
    if (shouldRefetch && !prevShouldRefetch.current) {
      prevShouldRefetch.current = true;
      refetchWithRefresh(); // Use refresh to exclude last ad
    } else if (!shouldRefetch) {
      prevShouldRefetch.current = false;
    }
  }, [shouldRefetch, refetchWithRefresh]);
 
  if (isLoading) return <div>Loading ad...</div>;
  if (!ad) return null;
 
  return (
    <a 
      href={ad.url}  // Includes automatic click tracking
      target="_blank" 
      rel="noopener noreferrer"
      className="block p-4 border rounded-lg hover:shadow-lg transition"
    >
      {ad.imageUrl && (
        <img 
          src={ad.imageUrl} 
          alt={ad.title}
          className="w-full h-auto rounded mb-2"
        />
      )}
      <h3 className="font-bold text-lg">{ad.title}</h3>
      {ad.description && (
        <p className="text-sm text-gray-600 mt-1">{ad.description}</p>
      )}
      <span className="text-xs text-blue-600 mt-2 inline-block">AD</span>
    </a>
  );
}

Add Sidebar to Your Layout

MODIFY your chat component, add a sidebar for the display ad:

return (
  <div className="flex gap-4">
    {/* Your existing chat UI (keep as-is) */}
    <div className="flex-1">
      {/* Your messages, input, etc. */}
    </div>
    
    {/* ADD: New sidebar for display ad */}
    <aside className="w-80">
      <h2 className="text-xl font-bold mb-2">Sponsored</h2>
      {conversationId && <DisplayAdComponent shouldRefetch={shouldRefetchAd} />}
    </aside>
  </div>
);

Test Phase 2

npm run dev

Ask: “What are the best database tools?”

Validate:

  • AI response includes sponsored hyperlinks
  • Display ad visible in sidebar
  • Display ad updates after each AI response
  • Check EarnLayer dashboard for analytics

What You Accomplished

Phase 2 Complete! You now have hyperlinks + display ads.

  • Hyperlink ads in responses
  • Display ads in sidebar
  • Contextual ad updates
  • Full monetization experience

Advanced: Thinking Ads (Optional)

Show ads during AI loading states for additional revenue opportunities.

How Thinking Ads Work

Thinking ads are contextual ads shown while your AI is processing a request. Here’s the flow:

  1. User sends a message → Your thinking ad component mounts
  2. Component requests a thinking ad with a timeout (e.g., 4 seconds)
  3. Your AI calls the MCP server with conversation context (typically 0.5-2 seconds)
  4. MCP server finds relevant most ads and queues them
  5. Backend receives notification and returns the ad immediately
  6. Ad displays while AI continues processing

The thinkingAdTimeout parameter tells the backend how long to wait for the MCP server to queue contextual ads before falling back to a default ad.

Implementation

// CREATE: New thinking ad component
import { useDisplayAd } from '@earnlayer/sdk/react';
 
function ThinkingAdComponent() {
  // ADD: EarnLayer thinking ad hook with timeout
  const { ad, isLoading, refetchWithRefresh } = useDisplayAd({
    adType: 'thinking',
    autoFetch: true,  // Auto-fetch when component mounts
    thinkingAdTimeout: 4  // Wait up to 4 seconds for contextual ads
  });
 
  if (isLoading || !ad) return null;
 
  return (
    <a
      href={ad.url}
      target="_blank"
      rel="noopener noreferrer"
      className="inline-flex items-center gap-2 text-sm text-blue-600"
    >
      <span className="animate-pulse"></span>
      {ad.title}
    </a>
  );
}
 
// ADD: Use in your chat (only shown during AI thinking):
{isThinking && (
  <div className="flex items-center gap-2">
    <span>Thinking...</span>
    <ThinkingAdComponent />
  </div>
)}

Understanding thinkingAdTimeout

What it does:

  • Tells the backend to wait for the MCP server to populate contextual ads
  • Typical time before MCP is called by your LLM: 0.5-2 seconds
  • Recommended value: 4 seconds (gives ample time for network latency)

Why 4 seconds?

  • MCP call happens after AI starts processing (not instant)
  • MCP needs time to generate embeddings and find relevant ads
  • Network latency between your server, AI provider, and EarnLayer
  • 4 seconds ensures contextual ads appear consistently

What happens if timeout is reached?

  • Backend returns a fallback default ad (e.g., EarnLayer)
  • No error or blank state - users always see an ad

Can I use a shorter timeout?

  • Yes, but you may see more fallback ads
  • If your internal testing shows <2s consistently, you can try 2-3 seconds

Testing Thinking Ads

Ask: “What are the best cloud storage solutions?”

Expected behavior:

  1. Thinking ad appears immediately (within 100ms)
  2. Shows contextual ad related to cloud/storage (if MCP responds within 4s)
  3. Ad remains visible until AI response completes
  4. After AI responds, ad disappears and conversation continues

Troubleshooting:

  • If you always see fallback ads, check that Phase 1B is complete
  • Verify x-conversation-id header is being passed in MCP calls

Advanced: Auto-Refresh Display Ads

Display ads can automatically refresh to show different ads from the queue.

function AutoRefreshBannerAd() {
  const { ad, isLoading, refetchWithRefresh } = useDisplayAd({
    adType: 'banner',
    autoFetch: false
  });
 
  const [refreshCount, setRefreshCount] = useState(0);
  const MAX_REFRESHES = 3; // Show 4 total ads: initial + 3 refreshes
 
  // Auto-refresh every 5 seconds
  useEffect(() => {
    if (!ad || isLoading || refreshCount >= MAX_REFRESHES) return;
 
    const timer = setTimeout(() => {
      refetchWithRefresh();
      setRefreshCount(prev => prev + 1);
    }, 5000); // 5 seconds
 
    return () => clearTimeout(timer);
  }, [ad?.id, isLoading, refreshCount, refetchWithRefresh]);
 
  if (isLoading || !ad) return null;
 
  return (
    <a 
      href={ad.url}
      target="_blank" 
      rel="noopener noreferrer"
      className="block p-4 border rounded-lg hover:shadow-lg transition"
    >
      {ad.imageUrl && (
        <img 
          src={ad.imageUrl} 
          alt={ad.title}
          className="w-full h-auto rounded mb-2"
        />
      )}
      <h3 className="font-bold text-lg">{ad.title}</h3>
      {ad.description && (
        <p className="text-sm text-gray-600 mt-1">{ad.description}</p>
      )}
    </a>
  );
}

Thinking Ad Auto-Refresh

function AutoRefreshThinkingAd() {
  const { ad, isLoading, refetchWithRefresh } = useDisplayAd({
    adType: 'thinking',
    autoFetch: true,
    thinkingAdTimeout: 4
  });
 
  // Auto-refresh every 5 seconds while ad is visible
  useEffect(() => {
    if (!ad || isLoading) return;
 
    const timer = setTimeout(() => {
      refetchWithRefresh();
    }, 5000); // 5 seconds
 
    return () => clearTimeout(timer);
  }, [ad?.id, isLoading, refetchWithRefresh]);
 
  if (isLoading || !ad) return null;
 
  return (
    <a
      href={ad.url}
      target="_blank"
      rel="noopener noreferrer"
      className="inline-flex items-center gap-2 text-sm text-blue-600"
    >
      <span className="animate-pulse"></span>
      {ad.title}
    </a>
  );
}

Best Practices:

  • Use refetchWithRefresh() to avoid showing the same ad twice
  • Limit refreshes to prevent overwhelming users (recommended: 3-5 refreshes)
  • Consider user experience - don’t refresh too frequently
  • Timer automatically resets when ad changes (new ad loaded)

Next Steps

Advanced Implementation

For production-ready patterns including:

  • Thinking ads with minimum display duration
  • Inline banner ad placement
  • Race condition prevention
  • Configuration options

Advanced Display Ad Implementation

Monitor Performance

Check your earnings and analytics:

EarnLayer Dashboard

Join the Community

Get help and share feedback:

Troubleshooting

Display ads not showing

  • Make sure conversationId exists before rendering display ads
  • Check Network tab - is /api/earnlayer/displayad/... returning 200?
  • Try manually calling refetch() from useDisplayAd

Ads not updating after messages

Verify the shouldRefetch state is toggling correctly. Check with:

useEffect(() => {
  console.log('shouldRefetch:', shouldRefetch);
}, [shouldRefetch]);

Wrong ad type showing

Check the adType parameter in useDisplayAd. Valid types:

  • hyperlink
  • thinking
  • banner

No ads available / Empty ad queue

If you’re seeing “No ads available”:

  1. Check demo mode: Enable demo mode for testing: initializeConversation({ demoMode: true })
  2. Check partnerships: In production, ensure you have partnerships configured

Learn more about Demo Mode