rudra-js

Quick start

Put a block of product recommendations on a page, with no model and no API key. Then turn the model on and check that it answered.

What you need

  • Node 22.12 or later.
  • React 18 or 19.

The steps below use a new Next.js app, but any React setup works. Create the app and move into it:

npx create-next-app@latest my-shop --yes --no-cache-components
cd my-shop

--yes takes the defaults for everything else. Keep --no-cache-components. With Cache Components on, the sample doesn't build. If your app already has it on, see Troubleshooting.

Install

npm install @rudra-js/core@latest @rudra-js/react@latest @rudra-js/attested@latest

Run the same line to upgrade. Keep @latest on every name. The packages are published together. Without @latest, npm can leave one behind and fail with ERESOLVE.

Every package is under the @rudra-js scope. The rudra-js package on npm, with no @, belongs to someone else.

Write the page

import { createComponentGenerator, parseTrackingInput } from '@rudra-js/core';
import { RudraComponent } from '@rudra-js/react';

const catalog = [
  { sku: 'A-1', title: 'Cast iron skillet', category: 'Cookware', price: 39, currency: 'USD' },
  { sku: 'A-2', title: 'Enamel dutch oven', category: 'Cookware', price: 89, currency: 'USD' },
  { sku: 'A-3', title: 'Chef knife', category: 'Knives', price: 55, currency: 'USD' },
];

async function recommendations() {
  // Passing no provider means no API key and no spend.
  // Core builds the block itself, and the same input always gives the same block.
  const generator = createComponentGenerator({ provider: null });

  // Parsing fills in what you left out and rejects anything that doesn't belong.
  // Always pass the parsed candidates to the renderer, not your raw objects.
  const input = parseTrackingInput({
    user: { id: 'shopper-1' },
    context: { surface: 'pdp', currentSku: 'A-1', currentCategory: 'Cookware' },
    candidates: catalog,
    signals: { cart: [{ sku: 'A-2', at: Date.now() }] },
  });

  const spec = await generator.generate(input);

  return <RudraComponent spec={spec} products={input.candidates} locale="en-US" />;
}

export default recommendations;

The catalog holds three products. The shopper is looking at the skillet, and the dutch oven is already in their cart.

The shopper is shown the chef knife, under the heading “Goes with your cart”. The skillet is left out because it's the page they're on. The dutch oven is left out because it's already in their cart. No model was asked, and nothing was billed.

Run it

Replace app/page.tsx with the sample. Its last line, export default recommendations, makes it the page. Start the dev server and open http://localhost:3000.

npm run dev

To check the block is there, look for its wrapper in the HTML:

curl -s localhost:3000 | grep -o 'data-rudra-source="fallback"'

Outside Next.js, pass the element to renderToString from react-dom/server: renderToString(await recommendations()). Rendering with renderToString shows a full example.

Turn the model on

Add the Anthropic adapter to the install line:

npm install @rudra-js/core@latest @rudra-js/react@latest @rudra-js/attested@latest @rudra-js/anthropic@latest

Put your API key in the ANTHROPIC_API_KEY environment variable. In Next.js, add it to .env.local. Then change the page to this:

import { createAnthropicProvider } from '@rudra-js/anthropic';
import { createComponentGenerator, parseTrackingInput } from '@rudra-js/core';
import { RudraComponent } from '@rudra-js/react';

const catalog = [
  { sku: 'A-1', title: 'Cast iron skillet', category: 'Cookware', price: 39, currency: 'USD' },
  { sku: 'A-2', title: 'Enamel dutch oven', category: 'Cookware', price: 89, currency: 'USD' },
  { sku: 'A-3', title: 'Chef knife', category: 'Knives', price: 55, currency: 'USD' },
];

const generator = createComponentGenerator({
  provider: createAnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY! }),
  onEvent(event) {
    console.log(event.source);
  },
});

async function recommendations() {
  const input = parseTrackingInput({
    user: { id: 'shopper-1' },
    context: { surface: 'pdp', currentSku: 'A-1', currentCategory: 'Cookware' },
    candidates: catalog,
    signals: { cart: [{ sku: 'A-2', at: Date.now() }] },
  });

  const spec = await generator.generate(input);

  return <RudraComponent spec={spec} products={input.candidates} locale="en-US" />;
}

export default recommendations;

Check it worked

Reload the page and watch the terminal running npm run dev. You want to see llm.

  • llm means the model answered.
  • cache means an earlier answer came from the cache. A second reload within a minute shows this.
  • fallback means core built the block without the model.

A model that times out or fails still gives you a page, with the same block you saw before. So check source before you call it done. If it says fallback, see Every block looks the same and source is fallback.

Troubleshooting

Next.js encountered the unstable value Date.now() while prerendering

Your app has Cache Components on. The sample and generate both read the clock while the page renders, so npm run build stops. npm run dev still shows the block, but prints the same error.

Delete cacheComponents: true and partialPrefetching: true from next.config.ts, then build again. Leaving partialPrefetching in fails the build too.

Where to go next