@rulecms/widget-react
v22.27.0
Published
React widget component for RuleCMS
Readme
@rulecms/widget-react
React widget component for RuleCMS
Installation
npm install @rulecms/widget-reactUsage
import { RuleCMSWidget } from '@rulecms/widget-react';
function App() {
return (
<RuleCMSWidget publishedKey="your-widget-key" />
);
}Let a ruleset pick the widget
Instead of one publishedKey, pass a published ruleset key and the
parameters its rules read. The SDK POSTs them to the resolve endpoint and
renders whichever widget the ruleset selects (or the ruleset's default when
nothing matches):
import { RuleCMSWidget } from '@rulecms/widget-react';
import type { WidgetSelection } from '@rulecms/widget-react';
<RuleCMSWidget
rulesetPublishedKey="{environmentId}---ruleset-…"
params={{ locale, path: location.pathname, user: { plan } }}
onSelection={(selection: WidgetSelection) =>
analytics.track('widget_selected', selection)
}
/>paramsis any JSON object, sent as-is; nested objects are flattened on the server (user.plan). A new object with the same values does not refetch.onSelectionreceives{ outcome, ruleId, variantId, widgetKey, widgetPublishedKey, rulesetVersion, … }once per resolve. The same object is returned asselectionfromuseRuleCMSWidget.fallbackPublishedKey(optional) is fetched through the plain GET only when the resolve request fails at the network level or with a 5xx — never on a 4xx such as a bad token or unknown ruleset.dev.tokens resolve the draft ruleset on rulecms.com; the key may be the bareruleset-…id or a published key (the environment prefix is stripped).
Anonymous id and consent
Experiments need a stable per-visitor unit. Unless params.anonId is set or
you pass anonymousId={false}, the widget mints a UUID once, stores it in
first-party localStorage under rulecms_anon_id, and sends it as
params.anonId. It is never sent anywhere else and carries no personal data,
but it is a persistent identifier: where your consent regime requires opt-in,
render with anonymousId={false} until consent is given, and clear
localStorage key RULECMS_ANONYMOUS_ID_STORAGE_KEY (exported) when consent
is withdrawn. Server-side entry points never invent an id — pass your own
params.anonId (for example from a cookie) if you want consistent bucketing.
The request is a cross-origin POST with Content-Type: text/plain, which is
CORS-safelisted, so the browser sends no preflight. contentType:
'application/json' exists for server-to-server calls only.
Server-side rendering
Fetch widget data on the server (or at build time), then render with pre-fetched data so images and content appear in the initial HTML.
1. Fetch on the server
import { fetchRuleCMSWidget } from '@rulecms/widget-react/server';
import { RuleCMSWidget } from '@rulecms/widget-react';
// Use a server-only env var — never NEXT_PUBLIC_* for the token
const data = await fetchRuleCMSWidget({
publishedKey: 'your-widget-key',
token: process.env.RULECMS_TOKEN!,
endpoint: process.env.RULECMS_ENDPOINT, // optional; widget-cache recommended
fetchOptions: {
// Next.js App Router cache control, e.g.:
next: { revalidate: 60 },
},
});2. Render with pre-fetched data
<RuleCMSWidget mode="pre-fetched" publishedKey="your-widget-key" initialData={data} />No token is needed on the client in pre-fetched mode — the widget does not
refetch in the browser.
Rulesets on the server
fetchRuleCMSWidget and RuleCMSWidgetServer accept the same
rulesetPublishedKey + params pair. The result carries selection; use its
widgetPublishedKey as the publishedKey for mode="pre-fetched":
const data = await fetchRuleCMSWidget({
rulesetPublishedKey: process.env.RULECMS_RULESET_KEY!,
params: { locale, path, anonId: cookies().get('anon_id')?.value },
token: process.env.RULECMS_TOKEN!,
});
<RuleCMSWidget
mode="pre-fetched"
publishedKey={data.selection!.widgetPublishedKey}
initialData={data}
/>The resolve request is a POST, so Next.js's fetch data cache never stores it and
fetchOptions.next.revalidate is a no-op there (it still applies to the
publishedKey GET). Cache the returned data yourself if needed.
Next.js App Router example
// app/page.tsx
import { fetchRuleCMSWidget } from '@rulecms/widget-react/server';
import { RuleCMSWidget } from '@rulecms/widget-react';
export default async function Page() {
const data = await fetchRuleCMSWidget({
publishedKey: process.env.RULECMS_PUBLISHED_KEY!,
token: process.env.RULECMS_TOKEN!,
fetchOptions: { next: { revalidate: 60 } },
});
return (
<RuleCMSWidget mode="pre-fetched" publishedKey={process.env.RULECMS_PUBLISHED_KEY!} initialData={data} />
);
}Endpoint recommendation
For production, point endpoint at the widget-cache service
(https://widget-cache.rulecms.com) for faster server fetches. The default
(relative /api/v1/c/widget/get) is unchanged for backward compatibility.
Zero-JS widgets with RuleCMSWidgetServer (App Router)
On Next.js App Router (or any React Server Components environment),
RuleCMSWidgetServer fetches and renders the widget entirely on the server —
no widget JavaScript ships to the browser:
// app/page.tsx — a Server Component (no 'use client')
import { RuleCMSWidgetServer } from '@rulecms/widget-react/server';
export default function Page() {
return (
<RuleCMSWidgetServer
publishedKey={process.env.RULECMS_PUBLISHED_KEY!}
token={process.env.RULECMS_TOKEN!}
fetchOptions={{ next: { revalidate: 60 } }}
errorFallback={<p>Content is temporarily unavailable.</p>}
/>
);
}Error semantics: if the fetch fails, the component logs the error and renders
errorFallback (default: nothing) — a CMS outage never breaks your page.
Which server API should I use?
| | RuleCMSWidgetServer | fetchRuleCMSWidget + mode="pre-fetched" |
|---|---|---|
| Widget JS in browser | none | yes (hydrates) |
| Environments | App Router / RSC only | any React SSR (App/Pages Router, Remix, Express…) |
| Data control | fetch happens inside the component | you own the fetch (share data, custom caching) |
See __docs__/move-to-ssr/PLAN-move-to-ssr.md for the full SSR roadmap.
Development
Install dependencies
npm installRun development mode
npm run devRun tests
npm testRun Storybook
npm run storybookBuild for production
npm run buildPublishing
Increment version
# Patch version (1.0.0 -> 1.0.1)
npm run version:patch
# Minor version (1.0.0 -> 1.1.0)
npm run version:minor
# Major version (1.0.0 -> 2.0.0)
npm run version:majorPublish to npm
Push the version tag. GitHub Actions publishes via trusted publishing
(.github/workflows/publish.yml). Do not run npm publish locally.
git push && git push --tags
npm view @rulecms/widget-react versionLicense
MIT
