rudra-js

Bringing your own ranking

By default, core puts the products in order by the shopper's signals. You might already have a ranking you trust, such as a bought-together list, a model trained on your own orders or a row a merchandiser picked by hand. Pass rank: 'given', and the order you send is the order that renders.

Keeping your order

import {
  createComponentGenerator,
  parseTrackingInput,
  type TrackingInputDraft,
} from '@rudra-js/core';

const generator = createComponentGenerator({
  provider: null,
  rank: 'given',
});

const fromYourRecommender: TrackingInputDraft['candidates'] = [
  {
    sku: 'SK-201',
    title: 'Merino Hiking Socks',
    category: 'Socks',
    price: 22,
    reason: 'Bought together with this shoe',
  },
  { sku: 'GT-310', title: 'Trail Gaiters', category: 'Accessories', price: 35 },
  { sku: 'TR-104', title: 'Ridgeline Trail Shoe', category: 'Trail Running', price: 139 },
];

export async function boughtTogether(shopperId: string) {
  const input = parseTrackingInput({
    user: { id: shopperId },
    context: { surface: 'pdp', currentSku: 'TR-102', currentCategory: 'Trail Running' },
    candidates: fromYourRecommender,
  });
  const spec = await generator.generate(input);
  return { spec, products: input.candidates };
}
  • fromYourRecommender stands for the list your ranking returns, best first. parseTrackingInput keeps it in that order as candidates. The socks show first, then the gaiters, then the trail shoe.
  • Core still leaves out any product this shopper can't be shown, such as one in their basket. It checks every signal you sent, so a shopper with a long order history is covered too.
  • rank works in the default mode and in the block core builds without a model. In per-shopper mode the model picks the order instead.

Writing the line under each product

Each candidate can carry its own reason, the line shown under its card. Use it when your ranking knows something the signals don't. In the sample, the socks say “Bought together with this shoe”.

Core shows your reason as you wrote it, the same as the product title. It doesn't check it for claims. That holds in the default mode and in the block core builds without a model. In per-shopper mode the model writes every reason, and core checks each one.

With no reason of your own, core writes one from the shopper's signals, such as “More in Trail Running” or “Goes with what is in your cart”. Core doesn't check its own lines either.

What each line can claim

Each product in the block also carries a basis, a code for why it's there. As the block is served, core checks the basis against the shopper's signals. When the check fails, core drops the line and the badge under that product.

BasisCore keeps it when
most_viewedThe product is among the ones the shopper viewed most.
similar_to_currentThe product is in the category being browsed.
liked_categoryThe shopper bought, liked, carted or viewed something in its category. Browsing a category page doesn't count.
complements_cartThe basket isn't empty.
complements_purchaseThe shopper has bought something before.
popularAlways. It claims nothing.

The two complements bases prove less than they read. Core can't know that one product goes with another. So “Goes with what is in your cart” only means the basket has something in it. If your ranking knows what goes together, say so in reason.