Developer guide
How to Collect Feedback in React & Next.js
Add feedback collection to a React or Next.js app with a drop-in widget or a direct API call โ including the App Router and CSP details that trip people up.
You can collect feedback in a React or Next.js app two ways: drop in the Pollenate script tag for a zero-config widget, or call the collect API directly from your own components for full control over the UI. This guide covers both, including the Next.js App Router specifics that trip people up.
Option 1: The drop-in widget (fastest)
For most apps, the script-tag widget is the quickest path. In a standard React app, add it once near your root so it persists across client-side navigation.
In Next.js (App Router), use the next/script component in your root layout so it loads after hydration:
// 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://pollenate.dev/widget.js"
data-inbox-key={process.env.NEXT_PUBLIC_POLLENATE_INBOX_KEY}
data-api-key={process.env.NEXT_PUBLIC_POLLENATE_API_KEY}
data-type="emoji"
strategy="afterInteractive"
/>
</body>
</html>
);
}
In Vite or Create React App, add the same script to index.html, or inject it in a top-level useEffect if you prefer to keep it in code.
Option 2: Call the collect API directly
When you want feedback collection to live inside your own design system, skip the widget and POST to the collect endpoint from a component. This gives you complete control over the markup and interaction.
import { useState } from 'react';
const API = 'https://api.pollenate.dev';
export function FeedbackButtons({ page }: { page: string }) {
const [sent, setSent] = useState(false);
async function submit(score: number) {
await fetch(`${API}/collect`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Pollenate-Key': import.meta.env.VITE_POLLENATE_API_KEY,
},
body: JSON.stringify({
inboxKey: import.meta.env.VITE_POLLENATE_INBOX_KEY,
type: 'thumbs',
score,
meta: { page },
}),
});
setSent(true);
}
if (sent) return <p>Thanks for the feedback!</p>;
return (
<div>
<span>Was this helpful?</span>
<button onClick={() => submit(1)} aria-label="Yes">๐</button>
<button onClick={() => submit(0)} aria-label="No">๐</button>
</div>
);
}
For typed projects, the @pollenate/react package ships a fully typed drop-in component and hooks so you don't have to hand-roll the fetch call.
Where should you put your API keys?
Use a collect-scoped API key for anything that runs in the browser. The collect scope can only submit feedback โ it can't read data or change settings โ so it's safe to expose in client-side code, the same way analytics keys are. Keep admin-scoped keys server-side only.
In Next.js, prefix browser-exposed values with NEXT_PUBLIC_. In Vite, prefix them with VITE_.
Handling client-side navigation
Single-page apps don't trigger full page loads, so make sure feedback context updates as the route changes. If you call the API directly, pass the current path in meta on each submission (as shown above). If you use the widget, it reads window.location at submit time, so it already captures the active route.
Content Security Policy
If your app sets a strict CSP, allow Pollenate's origins so the widget and API calls aren't blocked. Add to your policy:
script-src https://pollenate.devconnect-src https://api.pollenate.dev https://pollenate.dev
A blocked widget usually shows a "Failed to fetch" error in the console โ that's almost always a missing CSP entry.
Next steps
Once feedback is flowing, set up an automation so new responses post to Slack or open a GitHub issue, and use AI semantic search to find recurring themes across everything your users tell you.