Skip to content

Quick Start

Build and preview a small Vite + Preact experiment in Adobe Target. The walkthrough takes about 15 to 20 minutes, including time to find a stable selector on the target page.

Prerequisites

Use Node 24 and pnpm >=10.26.0. If you have not set them up yet, complete Installation first.

What you will build

  1. Scaffold a button experiment.
  2. Use DevTools to find a stable CSS selector on the target page.
  3. Choose where the experiment injects relative to that element (afterbegin, beforeend, and so on).
  4. Paste a working bundle into Adobe Target and see the button on the page.

Choose your path

GoalUse this path
First experimentFollow this page end to end.
Existing Target activityScaffold, start watch mode, then paste into the matching variation.
Just exploringOpen Playground.
Preparing releaseSkip to Run and Ship.

Step 1: Scaffold the project

bash
npx @sogody/experiment-framework my-first-experiment
cd my-first-experiment
nvm use

When prompted:

PromptChooseWhy
Number of variations1Keeps the first bundle focused.
Window namespacesgdUses the team default.
Include emergency brakeYesKeeps the safety hook available.
Enable E2E testingNoAdd Playwright after the core loop works.

If you are unsure, keep the default answer.

Step 2: Know the two files you will edit first

The scaffold creates the full project. Start with these two files:

FileWhat it controls
src/config.jsWhere the experiment injects (selectors) and what the button says (buttonText).
src/js/v1/index.jsxHow the variation runs: wait for DOM → mount container → render UI → attach tracking.

The variation entry point follows a fixed order:

jsx
runScript(async () => {
    const container = mountExperiment(selectors.primary, selectors.fallbacks, 'afterbegin', {
        className: style.root,
        dataset: { experiment: 'my-first-experiment' },
    });
    if (!container) return;

    render(<ExperimentButton text={buttonText} />, container);

    setupTracking(container, {
        label: 'my-first-experiment: v1 button clicked',
        selector: 'button',
    });
});
  • runScript() waits until the page DOM is ready.
  • mountExperiment() finds your selector and inserts a wrapper div.
  • render() puts the Preact button inside that wrapper.
  • setupTracking() runs after render so the button exists.

If the button does not appear, check the selector and mount position first.

Step 3: Find the mount point with DevTools

Before editing code, inspect the real target page in the browser.

Open the target page

Use the URL from your Adobe Target activity, or the scaffold default in experiment.config.js:

js
// experiment.config.js
export default {
    targetUrl: 'https://www.samsung.com/uk/smartphones/all-smartphones/',
    // ...
};

Open that URL in Chrome or Edge.

Inspect the injection anchor

  1. Open DevTools (F12 or Cmd+Option+I on macOS).
  2. Click the element picker (cursor icon in the top-left of DevTools).
  3. Click the page region where the experiment should appear, such as a product grid, hero, or filter bar.
  4. In the Elements panel, note the highlighted node and its stable attributes.

Prefer selectors that survive page reloads and market differences:

PreferAvoid
[data-testid="…"], [data-component="…"]Random hashed classes such as .css-1a2b3c
Semantic wrappers: main, [role="main"]nth-child chains that break when optional modules load
A unique class on a layout containerIDs that change per session

Test the selector

In the DevTools Console tab, verify the selector matches exactly one intended element:

js
document.querySelector('[data-testid="product-list"]')
// → should return the element you want, not null

If it returns null, refine the selector. If it returns the wrong element, pick a more specific anchor.

Choose the mount position

mountExperiment() inserts a wrapper relative to the matched element. Choose the position based on where the UI should land:

PositionWhere the wrapper goesTypical use
'afterbegin' (scaffold default)Inside the target, before its first childInject at the top of a section
'beforeend'Inside the target, after its last childAppend inside a container
'beforebegin'Immediately before the target elementInsert a full-width row above a block
'afterend'Immediately after the target elementInsert below a section

Visual model for target element <section class="hero">:

text
beforebegin →  [ wrapper ] <section>…</section>
afterbegin  →  <section> [ wrapper ] …children… </section>
beforeend   →  <section> …children… [ wrapper ] </section>
afterend    →  <section>…</section> [ wrapper ]

Use DevTools to confirm the anchor has room for your UI. A crowded flex row may need 'afterend' instead of 'afterbegin'.

Step 4: Configure selectors and mount position

Set selectors in src/config.js

Replace the placeholders with the selector you tested in DevTools:

js
export const selectors = {
    primary: '[data-testid="product-list"]',
    fallbacks: ['main', 'body'],
};

export const buttonText = 'Shop now';

Rules for the selector chain:

  • primary is the preferred injection anchor. Keep it as specific as the page allows.
  • fallbacks are tried in order after primary. Use them when templates differ across markets.
  • Do not rely on body unless you intentionally want a last-resort full-page mount.

Change the mount position when needed

The third argument to mountExperiment() in src/js/v1/index.jsx controls placement. The scaffold defaults to 'afterbegin'.

Example: inject below the product list instead of inside it:

jsx
const container = mountExperiment(selectors.primary, selectors.fallbacks, 'afterend', {
    className: style.root,
    dataset: { experiment: 'my-first-experiment' },
});

Save both files. You will rebuild in the next step.

Step 5: Start watch mode

bash
pnpm start 0

You should see:

text
v1-index.jsx copied to clipboard

TIP

Every save rebuilds the bundle and copies the latest output to your clipboard.

Alternative: open the target page with live injection (uses targetUrl from experiment.config.js):

bash
pnpm live

The overlay shows which selector matched. See Run and Ship - Live injection for flags such as --url and --overlay hidden.

Step 6: Paste into Adobe Target

  1. Open your Adobe Target activity.
  2. Open the Custom Code editor for variation 1.
  3. Paste the clipboard contents.
  4. Save and refresh the preview page.

WARNING

Paste the bundle into the matching Target variation. pnpm start 0 builds v1-index.jsx.

Step 7: Verify and fix common issues

SymptomLikely causeFix
Nothing rendersSelector does not match on this page/marketRe-test in DevTools console on the preview URL; update selectors.primary
UI appears in the wrong placeMount position does not match layoutChange 'afterbegin''afterend' or 'beforeend' in src/js/v1/index.jsx
UI flashes then disappearsSPA re-executes Target codeAdd a dedup guard - see mountExperiment SPA dedup
Button missing but bundle runsmountExperiment returned nullCheck fallback order; confirm if (!container) return is present
Click not trackedTracking runs before renderKeep setupTracking() after render()

After each fix, save the file, wait for the clipboard message, paste the new bundle, and refresh the Target preview.

Step 8: Build for shipping

bash
pnpm build

The production bundle is written to:

text
dist/v1-index.jsx

Before handoff, also run pnpm format and pnpm lint. See Run and Ship for the full release checklist.

If your team uses coding agents, AI Project Support explains how to add local instruction files.

Next steps

Internal tool - Samsung / Sogody experimentation team

Help improve the frameworkShare an idea or friction you encountered.Share framework feedback(opens in a new tab)