Monorepo with two aligned Formcentric integration demos:
nuxt/(@formcentric/client-vue)react/(@formcentric/client-react)
Both apps share the same route behavior, runtime config source, and mostly shared translation keys.
Install once at repository root:
pnpm installRun either app in cloud mode:
pnpm -C nuxt dev-cloud
pnpm -C react dev-cloudRun either app in headless mode:
pnpm -C nuxt dev-headless
pnpm -C react dev-headlessThis repo is dev-server oriented for both apps (no app-level build/preview scripts are required for normal usage).
Both apps read runtime config from root config/:
config/cloud.config.jsonconfig/headless.config.json
Example cloud.config.json:
{
"fcForms": [
{ "id": "5a335e0a-d17b-4530-8934-79e5ebd18c39", "name": "Application" },
{ "id": "00000000-0000-0000-0000-000000000000", "name": "Contact" }
],
"fcUrl": "https://form.formcentric.com/headless-server/"
}Example headless.config.json:
{
"fcDataUrl": "http://localhost:8080",
"fcFormDefinition": "<encrypted form definition>"
}Both apps expose:
/: landing page (no form mount)/single-form: exactly one mounted form/forms: all configured cloud forms, or one headless form in headless mode
If two cloud IDs are configured in cloud.config.json, /forms renders two forms.
In cloud mode, Formcentric requests need to hit the configured cloud backend path (usually /headless-server).
The local dev proxy exists to keep those requests same-origin from the browser perspective.
What this solves:
- avoids CORS/preflight friction during local development
- keeps browser requests on your app origin while forwarding upstream to
fcUrl - keeps cloud-mode behavior consistent between Nuxt and React
How each app applies it:
- Nuxt:
nitro.devProxyinnuxt/nuxt.config.ts - React:
server.proxyinreact/vite.config.ts
Proxy is enabled in cloud mode and disabled in headless mode.
Each app intentionally uses only two form components:
- cloud form component (embed-id based)
- headless form component (
data-url+ encrypted definition)
This contract applies to both adapter packages:
@formcentric/client-react@formcentric/client-vue
- Treat adapters as restart-based integration bridges, not fully controlled form widgets.
- Use adapters only in client/browser runtime.
- Provide mount config via top-level adapter props.
- Built-in restart triggers are:
remountKeyembedIdformDefinition
- Most other config values are init-time props; changing them does not live-update a running instance.
- If init-time config changes must apply, bump
remountKey. - Theme assets are not bundled by the adapters. Provide CSS/template/variables (or runtime URLs) explicitly.
onReady/@readysignals completed initialization/start, not guaranteed first visible render.onError/@erroris the runtime/init error channel.
- Do not expect arbitrary prop updates to patch a running form.
- Do not use framework
keychanges to force reloads; useremountKey. - Do not pass newly created config objects every render and assume they apply live.
- Do not remount form components due to unrelated UI state if form continuity matters.
- Do not mix adapter lifecycle with separate manual lifecycle control for the same container.
- Avoid concurrent mounts with the same
embedIdunless intentionally designed and tested.
- Keep config objects stable (
useMemo, stable refs/computed patterns). - Use an explicit remount policy for language/theme/config changes.
- Keep routing/lifecycle simple: mount once per view, remount intentionally.
- Bundle/import theme assets once and keep theme source consistent per app.
- Use ready/error callbacks/events to drive loading/error UI states.
- Set explicit container sizing (
style/class) to reduce layout jumps.
- Nuxt details:
nuxt/README.md - React details:
react/README.md