Skip to content
Last updated

Embedding forms in Next.js

Examples use the App Router.

Client Components only

The script embed reads and writes to window and the DOM, so any component that calls formsapp() needs the 'use client' directive. The plain iframe embed has no such requirement and can render from a Server Component.

Iframe embed

export function CakeOrderForm() {
  return (
    <iframe
      title="Cake Order Form"
      src="https://eu.forms.app/form/69d4bd130b443bda40c8f65a"
      allowTransparency
      allowFullScreen
      allow="geolocation; microphone; camera"
      style={{ width: '100vw', minWidth: '100%', height: 600, border: 'none' }}
    />
  );
}

Script embed with next/script

Use next/script instead of manually injecting a <script> tag; it handles load scheduling and de-duplication for you.

'use client';

import Script from 'next/script';

declare global {
  interface Window {
    formsapp: new (
      formId: string,
      layout: string,
      options: Record<string, unknown>,
      domain: string,
    ) => void;
  }
}

export function CakeOrderPopup() {
  return (
    <>
      <button formsappid="69d4bd130b443bda40c8f65a" />
      <Script
        src="https://cdn.formsapp.io/embed.js"
        strategy="lazyOnload"
        onLoad={() => {
          new window.formsapp(
            '69d4bd130b443bda40c8f65a',
            'popup',
            {
              overlay: 'rgba(45,45,45,0.5)',
              button: { color: '#ff9e24', text: 'Click here!' },
              width: '800px',
              height: '600px',
              openingAnimation: { entrance: 'animate__fadeIn', exit: 'animate__fadeOut' },
            },
            'https://eu.forms.app',
          );
        }}
      />
    </>
  );
}

Reading URL parameters

Use useSearchParams from next/navigation, together with the answers option from Passing data to your form:

'use client';

import Script from 'next/script';
import { useSearchParams } from 'next/navigation';

export function ReferralForm() {
  const searchParams = useSearchParams();
  const ref = searchParams.get('ref') ?? '';

  return (
    <>
      <div data-formsapp-src="https://eu.forms.app/form/69d4bd130b443bda40c8f65a" />
      <Script
        src="https://cdn.formsapp.io/embed.js"
        strategy="lazyOnload"
        onLoad={() => {
          new window.formsapp(
            '69d4bd130b443bda40c8f65a',
            'standard',
            { width: '100vw', height: 'formHeight', answers: { '63ebad419442ad0448b9e9b6': ref } },
            'https://eu.forms.app',
          );
        }}
      />
    </>
  );
}
Wrap in Suspense

useSearchParams opts a route into client-side rendering for the part of the tree that calls it. Wrap the component in a <Suspense> boundary, per the Next.js guidance, to avoid deopting the whole page.

What's next

  • React for the Pages Router / plain React equivalent.
  • Embed options for every layout's settings.