Guides / Developers

Adding Spectare to your site

Five integration paths. Same atom library, same personalised result. Pick the one that fits your stack and you can be live today.

Visitor arrives

Qualify: classify intent

audience, stage, confidence

Assemble: pick atoms and components

Personalised page

Every path below is a different way to reach the same two calls.

All paths

Next.js SSR

Recommended

The highest-fidelity path. Personalised content is inlined in the HTML at first paint: no shimmer, no DOM swap, no layout shift. The server fetches the assembly during the React render pass and passes it as initial props. If the client classifies the same intent, nothing changes on load.

Requires your org API key from Settings. Available on all plans. For a deeper walkthrough with caching rules and troubleshooting, see the step-by-step SSR guide.

Install the package

npm install @spectare-personalisation/react

Fetch assembly in your Server Component

Add SPECTARE_API_KEY to your environment. Get the value from Spectare Settings. Pass UTM params through from searchParams so paid and social traffic gets intent-aware assembly.

// app/page.tsx (Server Component)
import { PersonalisedSection } from '@spectare-personalisation/react';

export default async function Page({
  searchParams,
}: {
  searchParams: Record<string, string>;
}) {
  const res = await fetch('https://spectare.ai/api/assemble/ssr', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.SPECTARE_API_KEY}`,
    },
    body: JSON.stringify({
      orgSlug: 'your-workspace-slug',
      pageHint: 'Visiting the homepage',
      utmSource: searchParams.utm_source,
      utmMedium: searchParams.utm_medium,
      utmCampaign: searchParams.utm_campaign,
    }),
    cache: 'no-store',
  });

  const { assembly, intent } = res.ok ? await res.json() : {};

  return (
    <main>
      <PersonalisedSection
        orgSlug="your-workspace-slug"
        initialAssembly={assembly}
        initialIntent={intent}
        pageHint="homepage"
      />
    </main>
  );
}

Place each zone in its natural position

Split the assembly by zone and place each one exactly where it belongs in your layout. zone="hero" renders HeroStatement or ImageCtaHero. zone="body" renders feature and content components. zone="proof" renders TestimonialCard. zone="cta" renders CtaStrip. The same assembly array is passed everywhere; the renderer filters by zone.

// app/page.tsx: place each zone in its natural position on the page
import { ComponentRenderer, PersonalisedSection } from '@spectare-personalisation/react';

export default async function Page({ searchParams }) {
  const res = await fetch('https://spectare.ai/api/assemble/ssr', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.SPECTARE_API_KEY}`,
    },
    body: JSON.stringify({
      orgSlug: 'your-workspace-slug',
      pageHint: 'Homepage',
      utmSource: searchParams.utm_source,
      utmMedium: searchParams.utm_medium,
    }),
    cache: 'no-store',
  });

  const ssrData = res.ok ? await res.json() : null;
  const assembly = ssrData?.assembly ?? [];

  return (
    <main>
      {/* hero zone: HeroStatement or ImageCtaHero */}
      <header>
        {assembly.length
          ? <ComponentRenderer assembly={assembly} zone="hero" />
          : <PersonalisedSection orgSlug="your-workspace-slug" zone="hero" />}
      </header>

      <YourExistingContent />

      {/* body zone: AtomCard, StatGrid, FeatureList, CodeBlock, ComparisonTable */}
      <section>
        {assembly.length
          ? <ComponentRenderer assembly={assembly} zone="body" />
          : <PersonalisedSection orgSlug="your-workspace-slug" zone="body" />}
      </section>

      {/* proof zone: TestimonialCard */}
      <aside>
        {assembly.length
          ? <ComponentRenderer assembly={assembly} zone="proof" />
          : <PersonalisedSection orgSlug="your-workspace-slug" zone="proof" />}
      </aside>

      {/* cta zone: CtaStrip */}
      <footer>
        {assembly.length
          ? <ComponentRenderer assembly={assembly} zone="cta" />
          : <PersonalisedSection orgSlug="your-workspace-slug" zone="cta" />}
      </footer>
    </main>
  );
}

Or map assembly to your existing markup

If you already have a hero section you want to personalise without adding wrapper components, pull the slot values directly from the assembly array. The assembly response is plain JSON: find the component by name, extract the slot, pass it into your existing HTML structure. Your default copy is the fallback if no assembly arrives.

// app/page.tsx (Server Component)
// Map assembly slots to your own existing markup, no wrapper components needed

const res = await fetch('https://spectare.ai/api/assemble/ssr', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Authorization: `Bearer ${process.env.SPECTARE_API_KEY}`,
  },
  body: JSON.stringify({
    orgSlug: 'your-workspace-slug',
    pageHint: 'Homepage',
    utmSource: searchParams.utm_source,
    utmCampaign: searchParams.utm_campaign,
  }),
  cache: 'no-store',
});

type AssemblyItem = { component: string; slots: Record<string, unknown> };
const { assembly = [] }: { assembly: AssemblyItem[] } = res.ok
  ? await res.json()
  : {};

// Pull the components you want and extract their slot values
const hero  = assembly.find(c => c.component === 'HeroStatement')?.slots ?? {};
const cta   = assembly.find(c => c.component === 'CtaStrip')?.slots    ?? {};

const primary = cta.primary as { label: string; href: string } | undefined;

return (
  <main>
    {/* Your existing HTML structure, personalised copy drops straight in */}
    <h1 className="hero-headline">
      {hero.headline as string ?? 'Default headline'}
    </h1>
    <p className="hero-sub">
      {hero.subheading as string ?? 'Default description copy.'}
    </p>

    <a href={primary?.href ?? '/signup'} className="btn-primary">
      {primary?.label ?? 'Get started'}
    </a>
  </main>
);

Component slot reference

HeroStatement: headline (string), subheading (string). CtaStrip: primary { label, href }, headline (string). StatGrid: stats { value, label }[]. See the API reference for all nine component types.

How zero-flicker works

The server fetches the assembly and passes it as initialAssembly to PersonalisedSection. The component renders immediately with that content. On the client, PersonalisedSection qualifies intent in the background. If the client classification matches the server (same audience and stage), no DOM swap occurs. If it differs, the component swaps in the client result silently.

WordPress plugin

No code

Two rendering modes in one plugin. Slots are client-side JavaScript and work with any caching setup. Zones are PHP server-rendered for content at first paint, no shimmer, but require an API key and disable page caching for that request.

See the full WordPress guide for caching configuration, Gutenberg block usage, shortcodes, page builders, and troubleshooting.

Installation

  1. 1

    Download the plugin zip from spectare.ai.

  2. 2

    In WordPress admin, go to Plugins, Add New, Upload Plugin. Install and activate.

  3. 3

    Go to Settings, Spectare. Enter your workspace slug and save.

  4. 4

    For zones only: paste your API key from Spectare Settings. This enables PHP server-side rendering.

  5. 5

    In Gutenberg, search for "Spectare Slot" (client) or "Spectare Zone" (server). Choose the slot or zone name and publish.

Method A: Slots

Client-side JavaScript

JS fills the div after load. Works with all caching plugins. No API key required. Brief shimmer while content arrives.

[spectare_slot name="primary"]
[spectare_slot name="supporting"]

<!-- Reserve height to prevent layout shift while content loads -->
[spectare_slot name="primary" height="200"]

Method B: Zones

PHP server-rendered

PHP fetches assembly at render. Content is in the HTML at first paint. Sets DONOTCACHEPAGE to bypass caching plugins.

[spectare_zone zone="hero"]
[spectare_zone zone="body"]
[spectare_zone zone="proof"]
[spectare_zone zone="cta"]

Zone areas and their components

hero

HeroStatement, ImageCtaHero

body

AtomCard, StatGrid, FeatureList, CodeBlock, ComparisonTable

proof

TestimonialCard

cta

CtaStrip

Script tag

Any site

Works on any site that runs JavaScript: Webflow, Shopify, Squarespace, plain HTML, or any framework. Add one tag to your head and place slot divs where you want personalised content to appear.

Step 1: add the script tag

Replace YOUR_WORKSPACE_SLUG with the slug from Spectare Settings.

<!-- Add to <head> on every page -->
<script
  src="https://spectare.ai/spectare.js"
  data-org="YOUR_WORKSPACE_SLUG"
  data-base-url="https://spectare.ai"
></script>

Step 2: add slots where you want personalised content

The script fills these empty divs with assembled atom content. Each slot name maps to the matching slot in your atom library.

<!-- Place where you want personalised content to appear -->
<div data-spectare-slot="primary"></div>
<div data-spectare-slot="supporting"></div>
<div data-spectare-slot="secondary"></div>

Step 3: target existing elements (optional)

If your page already has a headline, body copy, or CTA, personalise those elements in place without adding wrapper divs. Add data-spectare-target="slot:field" to any existing element.

:title

Replaces the element text content with the atom title. Uses textContent, not innerHTML.

:content

Sets innerHTML from the atom body. Use on a container element like a <p> or <div>.

:cta

Sets the button or link text. On <a> elements, also updates the href.

<!-- Personalise existing elements instead of adding wrapper divs -->
<!-- Use data-spectare-target="slot:field" on any existing element -->

<!-- :title replaces the element text with the atom title -->
<h1 data-spectare-target="primary:title">Default headline</h1>

<!-- :content sets innerHTML from the atom body HTML -->
<p data-spectare-target="primary:content">Default description copy.</p>

<!-- :cta sets the button text; on <a> elements it also sets href -->
<a href="/signup" data-spectare-target="primary:cta">Get started</a>

Conversion tracking (optional but strongly recommended)

Set a conversion URL pattern in Settings (e.g. /thank-you) and Spectare fires automatically. Or call manually from any event. Each conversion is attributed back to the assembly the visitor saw, which improves future rankings for that audience and stage.

<!-- Option 1: fire on page load when URL matches a thank-you page -->
<!-- Set your conversion URL pattern in Settings. Fires automatically. -->

<!-- Option 2: fire manually on any event -->
<script>
  spectare('conversion');
</script>

React component (client-side)

No server setup required. Drop PersonalisedSection anywhere in your React tree. It qualifies and assembles on mount and shows a brief loading shimmer while content arrives.

// app/components/MyPersonalisedSection.tsx
'use client';
import { PersonalisedSection } from '@spectare-personalisation/react';

export default function MyPersonalisedSection() {
  return (
    <PersonalisedSection
      orgSlug="your-workspace-slug"
      pageHint="homepage"
    />
  );
}

REST API

Call the API directly from any language or runtime. No SDK required. Standard JSON over HTTPS. Two calls to qualify and assemble, or one SSR call if you have an API key.

Step 1: qualify the visitor

Send a plain-language summary of the visitor's context. No auth required. Rate limited by IP.

POST https://spectare.ai/api/qualify/your-workspace-slug
Content-Type: application/json

{
  "summary": "developer evaluating personalisation tools for a Next.js marketing site"
}

// Response
{
  "token": "<signed-context-token>",
  "url": "https://spectare.ai/landing?ctx=<token>",
  "intent": {
    "userContext": { "audience": "developer", "stage": "consideration" }
  }
}

Step 2: assemble the page

Send the token from step 1. This endpoint streams the assembly as Server-Sent Events (data: lines ending with data: [DONE]), which suits the browser widget. From a backend that just wants one JSON object, prefer the SSR call below. Render with ComponentRenderer from @spectare-personalisation/react or map to your own components.

POST https://spectare.ai/api/assemble/components
Content-Type: application/json

{
  "token": "<signed-context-token>",
  "orgSlug": "your-workspace-slug"
}

// Streams Server-Sent Events: data: lines carry the assembly, ending with data: [DONE].
// Suits the browser widget. For one-shot JSON from a backend, use /api/assemble/ssr below.
// Assembled items look like (each carries a zone field):
{
  "assembly": [
    { "component": "HeroStatement", "slots": { "headline": "...", "subheading": "..." }, "zone": "hero" },
    { "component": "FeatureList",   "slots": { "items": [...] },                         "zone": "body" },
    { "component": "CtaStrip",      "slots": { "headline": "...", "primary": {...} },    "zone": "cta"  }
  ],
  "intent": { "userContext": { "audience": "developer", "stage": "consideration" } },
  "cached": false
}

Combine into one SSR call

Use POST /api/assemble/ssr to qualify and assemble in a single server-to-server request. Requires your org API key. Returns assembly, intent, and cached. Pass UTM params in the body to get intent-aware assembly for paid and social traffic.

Ready to integrate?

Free 14-day trial, no credit card required.