Why embedding matters
Section titled “Why embedding matters”A recommendation model that sits in a dashboard does nothing. Its value is realized only when recommendations appear on your pages, in your emails, and across your customer touchpoints. The way you embed recommendations affects performance, user experience, and how well the engine learns from visitor interactions.
This guide covers three embedding approaches: CMS blocks for no-code placement, API integration for full control, and client-side widgets for quick deployment. Each approach includes fallback handling so new visitors and edge cases are covered.
What you will do
Section titled “What you will do”- Add a recommendation block to CMS pages
- Integrate with the Recommendations API for custom rendering
- Implement client-side embedding with JavaScript
- Configure fallback strategies for cold-start scenarios
Add recommendation blocks to CMS pages
Section titled “Add recommendation blocks to CMS pages”The simplest approach is the CMS recommendation block. Content authors drag it onto any content area and configure which model it uses — no code changes required.
For content authors:
- Edit a page in the CMS editor
- In a content area, click Add Block
- Select Recommendation Block
- Configure the block:
- Model — Select the recommendation model to use (e.g., “Product Page - Similar Items”)
- Number of items — How many recommendations to display (typically 3-6)
- Display template — Choose a layout (grid, list, carousel)
- Publish the page
The block handles API calls, rendering, and tracking automatically. When a visitor views the page, the block fetches recommendations for the current context (the page or product being viewed) and renders them using the selected template.
Integrate with the Recommendations API
Section titled “Integrate with the Recommendations API”For full control over how recommendations appear, call the Recommendations API directly and render the results yourself.
Server-side integration
Section titled “Server-side integration”using System.Net.Http;
using System.Text.Json;
public class RecommendationService
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly string _baseUrl;
public RecommendationService(
HttpClient httpClient,
string apiKey,
string baseUrl)
{
_httpClient = httpClient;
_apiKey = apiKey;
_baseUrl = baseUrl;
}
public async Task<List<RecommendedItem>>
GetRecommendations(
string modelId,
string contextItemId,
string visitorId,
int count = 4)
{
var request = new HttpRequestMessage(
HttpMethod.Get,
$"{_baseUrl}/api/v1/recommendations"
+ $"?modelId={modelId}"
+ $"&contextItem={contextItemId}"
+ $"&visitorId={visitorId}"
+ $"&count={count}");
request.Headers.Add(
"Authorization", $"Bearer {_apiKey}");
var response = await _httpClient
.SendAsync(request);
response.EnsureSuccessStatusCode();
var json = await response.Content
.ReadAsStringAsync();
return JsonSerializer
.Deserialize<List<RecommendedItem>>(json);
}
}
public class RecommendedItem
{
public string Id { get; set; }
public string Title { get; set; }
public string ImageUrl { get; set; }
public string Url { get; set; }
public double Score { get; set; }
} API parameters:
| Parameter | Required | Description |
|---|---|---|
modelId | Yes | The ID of the recommendation model to query |
contextItem | Depends | The current item ID (required for “similar items” strategies, not for “personalized for you”) |
visitorId | Yes | The ODP visitor ID (from tracking cookie) |
count | No | Number of recommendations to return (default: 4, max: 20) |
Client-side integration
Section titled “Client-side integration”For client-side embedding, use the Recommendations JavaScript SDK. This approach works well for single-page applications and sites where you want recommendations to load after the initial page render.
// Initialize the Recommendations SDK
const recsClient = window.optimizely.recommendations.create({
apiKey: 'YOUR_PUBLIC_API_KEY',
trackerId: 'YOUR_ODP_TRACKER_ID'
});
// Fetch recommendations for the current page
async function loadRecommendations(containerId, modelId) {
const container = document.getElementById(containerId);
try {
const response = await recsClient.getRecommendations({
modelId: modelId,
contextItem: getCurrentPageId(),
count: 4
});
if (response.items.length === 0) {
// No recommendations available -- show fallback
renderFallback(container);
return;
}
renderRecommendations(container, response.items);
// Track impression for model learning
recsClient.trackImpression({
modelId: modelId,
items: response.items.map(item => item.id)
});
} catch (error) {
console.error('Recommendations failed:', error);
renderFallback(container);
}
}
function renderRecommendations(container, items) {
const html = items.map(item => `
<a href="${item.url}" class="rec-item"
data-rec-id="${item.id}">
<img src="${item.imageUrl}"
alt="${item.title}" />
<span>${item.title}</span>
</a>
`).join('');
container.innerHTML = `
<div class="rec-widget">
<h3>Recommended for you</h3>
<div class="rec-grid">${html}</div>
</div>
`;
// Track clicks for model learning
container.querySelectorAll('.rec-item')
.forEach(el => {
el.addEventListener('click', () => {
recsClient.trackClick({
modelId: modelId,
itemId: el.dataset.recId
});
});
});
} Implement fallback strategies
Section titled “Implement fallback strategies”Recommendations will fail or return empty results in predictable scenarios. Plan for each one.
| Scenario | Why it happens | Fallback approach |
|---|---|---|
| New visitor, no history | No behavioral data to personalize from | Show trending items or editorially curated picks |
| New item, no interactions | Item has not been viewed or purchased enough | Use content-based matching on item attributes |
| API timeout or error | Network issues, service degradation | Show cached recommendations or static content |
| All items excluded by rules | Overly aggressive business rules | Relax rules or show a different model’s output |
async function getRecommendationsWithFallback(
primaryModelId,
fallbackModelId,
contextItemId
) {
// Try the primary model first
try {
const primary = await recsClient
.getRecommendations({
modelId: primaryModelId,
contextItem: contextItemId,
count: 4
});
if (primary.items.length >= 2) {
return primary.items;
}
} catch (e) {
// Primary model failed, continue to fallback
}
// Fallback to trending model
try {
const fallback = await recsClient
.getRecommendations({
modelId: fallbackModelId,
count: 4
});
return fallback.items;
} catch (e) {
// Both models failed
return getCachedRecommendations();
}
}
function getCachedRecommendations() {
// Return a static set of curated items
// as the last resort fallback
const cached = localStorage
.getItem('last-good-recs');
return cached
? JSON.parse(cached)
: [];
} Track recommendation interactions
Section titled “Track recommendation interactions”For the engine to learn and improve, it needs to know when visitors see and click recommendations. Tracking is essential — without it, models cannot retrain effectively.
Required tracking events:
| Event | When to fire | What it tells the engine |
|---|---|---|
| Impression | When recommendations render in the viewport | Which items were shown to the visitor |
| Click | When a visitor clicks a recommended item | Which recommendations the visitor found relevant |
| Conversion | When a click leads to a purchase or goal completion | Which recommendations drove business outcomes |
The CMS recommendation block handles tracking automatically. If you use the API or client-side SDK, you must implement tracking yourself (as shown in the code examples above).
Performance considerations
Section titled “Performance considerations”- Load recommendations asynchronously. Do not block page rendering while waiting for the API. Render the page shell first, then populate the recommendation widget.
- Cache API responses. For server-side integration, cache recommendation results for 5-15 minutes per visitor-context combination. This reduces API calls without significantly impacting personalization quality.
- Lazy-load below-the-fold widgets. If recommendations appear further down the page, use intersection observer to fetch them only when the visitor scrolls near them.
- Set a timeout. Configure a 2-3 second timeout on API calls. If the API does not respond in time, show the fallback immediately rather than leaving an empty space.
Common issues
Section titled “Common issues”| Issue | Cause | Fix |
|---|---|---|
| Widget shows “No recommendations” | Model not published, or insufficient data | Publish the model and verify ODP tracking is sending events |
| Same recommendations on every page | Context item not passed to the API | Include the current page or product ID as contextItem |
| Recommendations not improving over time | Impression and click tracking not implemented | Add tracking events for impressions and clicks |
| Slow page load | Recommendations blocking page render | Switch to async loading with a loading skeleton |