Skip to content

SPA Frameworks

The Quick Start embed scans for [data-rocketlead-form] elements once on DOMContentLoaded. That works for static sites and CMS pages, but breaks on client-routed SPAs — the user navigates away, the container is removed, and on return the script doesn’t re-scan, leaving the new container empty.

For SPA frameworks, use the imperative window.rocketlead.forms.create() API. It returns a handle with an unmount() method you wire into your component’s lifecycle.

Load widget.js with defer so it downloads in parallel with HTML parsing but is guaranteed to execute before your framework hydrates:

<link rel="stylesheet" href="https://cdn.rocketlead.io/widget.css">
<script src="https://cdn.rocketlead.io/static/forms/widget.js" defer></script>

For Next.js App Router, place this in app/layout.tsx. For Pages Router, use _document.tsx.

'use client';
import { useEffect, useRef } from 'react';
export function RocketLeadForm() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const handle = window.rocketlead?.forms?.create({
target: ref.current!,
formId: 'your-form-id',
});
return () => handle?.unmount();
}, []);
return <div ref={ref} />;
}

The useEffect cleanup tears the form down when the component unmounts (route change, conditional rendering, etc.). Re-mounting on the next visit calls create() again.

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const el = ref(null);
let handle;
onMounted(() => {
handle = window.rocketlead?.forms?.create({
target: el.value,
formId: 'your-form-id',
});
});
onUnmounted(() => handle?.unmount());
</script>
<template>
<div ref="el"></div>
</template>
window.rocketlead.forms.create({
target: HTMLElement | string, // element or CSS selector (resolved via querySelector)
formId: string,
prefill?: Record<string, string | string[]>, // programmatic field prefill
hideFields?: string[], // field IDs / stable names to hide
}): { unmount: () => void };
FieldDescription
targetThe element (or selector) to mount the form into. Selector strings resolve to first match.
formIdThe form ID from the editor.
prefillOptional record of values to prefill at mount. Keys can be either field UUIDs or stable field names (name from the editor). See Prefill for the full cascade and precedence rules.
hideFieldsOptional array of field IDs or stable names to hide from the rendered form. Hidden fields are still validated — a hidden required field without a matching prefill entry will fail at submit.

Returns { unmount } — call it from your framework’s cleanup hook to tear down the form and free resources.

Inject UTM parameters captured server-side, hide them from the user, but still send them with the submission:

const params = new URLSearchParams(window.location.search);
useEffect(() => {
const handle = window.rocketlead?.forms?.create({
target: ref.current,
formId: 'lead-form',
prefill: {
utm_source: params.get('utm_source') ?? 'direct',
utm_campaign: params.get('utm_campaign') ?? '',
},
hideFields: ['utm_source', 'utm_campaign'],
});
return () => handle?.unmount();
}, []);

The user sees a clean form; the submission carries the UTM context.

create() is safe to call repeatedly with the same target — internally a WeakMap<Element, FormInstance> tracks the active mount per element, and a second create() on the same target unmounts the previous instance first. This means:

  • React StrictMode (which runs effects twice in dev) doesn’t cause duplicate mounts.
  • HMR re-runs are safe.
  • Conditional rendering that re-mounts the same container works.
  • Embedding Overview — Quick Start for non-SPA setups.
  • Stable Field Namesname="" selectors that work the same way for both Quick Start and SPA mounts.
  • Prefillcreate() accepts a prefill option for programmatic field population.
  • Troubleshooting — what to check when SPA mounts misbehave.