# Add a feedback widget to a Next.js app

> Load `https://escutaproduto.com/widget.js` once in `app/layout.tsx` with `<Script strategy="afterInteractive" data-key="pk_…" />`, then push an `identify` call from a client component to attach the signed-in user.

Source: https://escutaproduto.com/docs/nextjs
Last updated: 2026-10-09

## 1. Load the script in the root layout

`next/script` forwards `data-*` props to the script tag, so the widget reads its options as usual.

```tsx
// app/layout.tsx
import Script from "next/script";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://escutaproduto.com/widget.js"
          data-key="pk_your_product_key"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}
```

Loading it in the root layout keeps one widget instance across client-side navigations. The widget records `location.href` at the moment of sending, so each message has the right page URL even in a single-page app.

## 2. Identify the signed-in user

Render this client component anywhere inside your authenticated layout. The queue form works whether or not the widget has finished loading.

```tsx
// components/feedback-identity.tsx
"use client";
import { useEffect } from "react";

type User = { id: string; email: string; name?: string; plan?: string };

export function FeedbackIdentity({ user }: { user: User }) {
  useEffect(() => {
    const w = window as typeof window & { EscutaProduto?: { q?: unknown[][] } };
    w.EscutaProduto = w.EscutaProduto || { q: [] };
    (w.EscutaProduto.q ||= []).push(["identify", [user]]);
  }, [user]);
  return null;
}
```

## 3. Open the widget from your own UI (optional)

Add `data-trigger="none"` to the `<Script>` to hide the floating button, then use the `data-escuta-open` attribute on any element, including inside Server Components, because it needs no JavaScript of its own:

```tsx
<button data-escuta-open="bug">Report a bug</button>
```

## Content Security Policy

If your app sends a CSP header, allow the script and the API:

```text
script-src  'self' https://escutaproduto.com;
connect-src 'self' https://escutaproduto.com;
```

## Sending feedback from the server

For Server Actions, Route Handlers or background jobs, call the [REST API](/docs/api) directly instead of the widget. Requests without an `Origin` header are accepted from any server.

## Frequently asked questions

### Should I use next/script or a plain script tag in Next.js?

Use next/script with strategy="afterInteractive" in app/layout.tsx. It loads the widget once after hydration and keeps it across client-side navigations.

### Does the widget work with the Next.js App Router and Server Components?

Yes. The script is loaded once in the root layout, and the data-escuta-open attribute works on elements rendered by Server Components because it needs no client-side React code.
