Recommendations

A reader who finishes an article is at a fork: leave, or read something else. Recommendations are how you win that moment — up to three related pages, chosen for the visitor who is standing there, rendered by your own site.

Nothing about them is curated. There is no related-posts list to maintain and no third-party service involved: the matches are computed from the content of your own pages, so a page you publish tomorrow becomes recommendable on its own.

How a page gets picked

Four filters run before anything is ranked, and they are cumulative:

ConditionWhere it is setWhat happens if it fails
The page’s type has Enable Recommendations onSettings → Page typesThe page is never a candidate, on any page
The page is published, as of nowThe page itselfDrafts and pages scheduled for a future date are excluded
The page is not the one being readAutomaticExcluded — a page never recommends itself
The visitor has not already viewed itAutomatic, from the visitor’s own historySoft — see below

That last one is a preference, not a hard rule. Leed first tries to fill all three slots from pages this visitor has not seen. If fewer than three qualify, it fills the rest from the closest matches regardless of history, so the widget never renders with one card in it because a regular reader had already read everything good.

sequenceDiagram
    autonumber
    participant R as Reader's browser
    participant P as Your page
    participant API as Recommendations endpoint
    participant IX as Content similarity index
    participant T as Card template on the page

    R->>P: Loads a page carrying a recommendations element
    P->>P: Waits for the visitor's session to be ready
    P->>API: Recommendations for this page
    API->>API: Plan allows serving?
    API->>IX: Closest pages by content similarity
    IX-->>API: Ranked candidates
    API->>API: Drop this page, drop pages already read,<br/>drop unpublished and future-dated
    API-->>P: Up to three recommendations, as data
    P->>T: Fill the card template once per result
    Note over P,T: Zero results — the container hides itself,<br/>heading and all

Making pages eligible

Eligibility is a page-type setting: Settings → Page types, then the Enable Recommendations checkbox on the type you want.

Settings → Page types with a type expanded, showing the Enable Recommendations checkbox beside the required-field controls

Read the direction carefully, because it is the opposite of what most people assume: the checkbox makes pages of that type eligible to be recommended. It does not put a recommendations section on them. Where the cards appear is a template decision, covered below. Blog posts and articles usually want it on; utility and structural types — a login page, a legal notice — usually do not.

The checkbox sits alongside the rest of a type’s configuration in configuring a page type.

Making the cards look right

A recommendation card is filled from a template your site ships, one slot per field:

Card elementSource fieldWhat to set so it renders
Link targetThe page’s public URLNothing — resolved for you
TitleThe page titleAlways present
Publish dateThe page’s publish date, formatted with your site’s own date format and timezoneAlways present
Reading timeDerived from the page’s word count and your site’s words-per-minute settingAlways present
SummaryThe page’s summaryWrite one — an empty summary drops the slot
Feature imageThe page’s feature imageSet one — an empty image drops the slot
LabelsThe page’s labelsOptional; renders as a list
AuthorsThe page’s authorsOptional; renders each author’s full name

Rendered, each recommendation is a card carrying the fields in that table in a fixed order — feature image, title, date, reading time, then summary. Three cards sit in a row on a published page; a missing optional field drops its slot rather than leaving a gap.

Where the cards render

Put data-leed-recommendations on the element that should receive the cards. That is the whole placement API: any page with such an element fills it, and a page without one makes no request at all.

The attribute goes on the element the cards are appended to — the element’s own contents are left alone, so a heading placed inside the wrapper stays above them.

<section id="related" class="related-reading">
  <h2>Related reading</h2>
  <div data-leed-recommendations></div>
</section>

When there is nothing to recommend

If no recommendations come back, the widget hides itself rather than leaving a “Related reading” heading sitting over an empty box.

Which element gets hidden depends on how you placed it:

  • If the receiving element has an id and a wrapper with the matching {id}-container exists, that wrapper is hidden — heading and all. This is the long-standing convention, and it is why the id-based form has always been able to hide its own heading.
  • Otherwise the receiving element itself is hidden, and anything you placed outside it — including a heading — stays visible. Put the heading inside the element if you want it to disappear with the cards.

The hide is applied with !important so it wins against a display utility class on the container.

Measuring whether they work

Every link inside a recommendation card is stamped with hidden UTM context — carried as an attribute on the link rather than as query parameters in the URL, so your recommendation traffic is attributable without ugly links and without breaking anyone’s bookmark:

ParameterValue
utm_campaignleed
utm_sourceinternal
utm_mediumrecommendation
utm_termThe recommended page’s id
utm_contentThe link’s own text, capped at 100 characters

Those clicks land in the destination page’s attribution — open the page a recommendation pointed at, and the click shows up as a recommendation channel arrival. That is the right place to look, and the mechanism behind it is described under the hidden UTMs Leed stamps for you.

What you do not get is an impression count: recommendation cards are not element-view tracked, so there is no “shown 400 times, clicked 12” ratio to read. Click-through against the page’s own session count is the closest available measure. A dynamic CTA, which uses the same similarity engine with different rules, is view-tracked — that difference is one of the real reasons to reach for one rather than the other.

Below Growth, and on failure

Two different situations produce exactly the same result for your visitor, by design:

  • Below Growth, the endpoint returns an empty list with a success status and never touches the similarity index.
  • If the similarity service itself fails, the response is still an empty list rather than an error body.

In both cases the container hides itself and the page renders normally. Nothing is logged to the visitor’s console, nothing is half-drawn, and no upgrade prompt appears on your published site — an upgrade prompt on a customer-facing page would be advertising your billing status to your readers.

The flip side is that “no cards” is ambiguous from the outside. If you are on Growth and expecting cards, walk the four filters at the top of this page in order before suspecting an outage: the page type must be recommendable, the candidates must be published, and the index must have caught up with your last deployment.

ESC