# 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

```tsx
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`](https://nextjs.org/docs/app/api-reference/components/script) instead of manually injecting a `<script>` tag; it handles load scheduling and de-duplication for you.

```tsx
'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`](https://nextjs.org/docs/app/api-reference/functions/use-search-params) from `next/navigation`, together with the `answers` option from [Passing data to your form](/embeds/passing-form-data):

```tsx
'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](https://nextjs.org/docs/app/api-reference/functions/use-search-params#behavior), to avoid deopting the whole page.

## What's next

- [React](/embeds/examples/react) for the Pages Router / plain React equivalent.
- [Embed options](/embeds/embed-options) for every layout's settings.