Troubleshooting
The failure modes people hit most often, what each one looks like, and what actually fixes it.
Check the console first
The SDK never throws for configuration mistakes — it logs a specific message prefixed with Roastnest and degrades quietly so your page keeps working. Most of what follows is visible there in one line.
Nothing renders
Missing provider
“FeedbackWidget must be used within a RoastnestProvider” — the widget is outside the provider's subtree. This is the one configuration error that does throw, because there is no sensible way to continue. Move the provider up the tree until it wraps every widget.
Missing project ID
“projectId is required via RoastnestProvider in cloud mode” — cloud mode has nothing to identify your project with, so the widget UI is suppressed while your page renders normally. Pass projectId to the provider, or switch to mode="self-hosted".
Missing submit handler
“onFormSubmit is required in self-hosted mode” — self-hosted mode has nowhere to send submissions. Provide the handler, and remember it must resolve a boolean.
Server component boundary
In the Next.js App Router, passing a function like onFormSubmit from a server component fails — functions cannot cross the boundary. Wrap the provider in your own client component and render that from the layout.
"use client";
import { RoastnestProvider, FeedbackWidget } from "@roastnest/react";
export function Feedback({ children }) {
return (
<RoastnestProvider mode="self-hosted">
{children}
<FeedbackWidget
mode="self-hosted"
onFormSubmit={async (data) => {
/* ... */
return true;
}}
/>
</RoastnestProvider>
);
}Submissions fail
Content Security Policy
A Refused to connect error in the console means your CSP is blocking the API. The widget renders fine and then fails at the last step, which makes this look like a backend problem when it is not. Add the API origin to connect-src:
connect-src 'self' https://api.roastnest.com;That one origin covers screenshot uploads too — no S3 or CDN host is needed separately.
The handler resolves false
In self-hosted mode the user sees the error message whenever onFormSubmit resolves anything falsy. A handler that forgets to return, or returns the response object instead of res.ok, reports failure on a request that actually succeeded.
Referral widget renders nothing
In self-hosted mode the widget refuses to render when referralLink's hostname does not match window.location.hostname, and says so in the console. This is deliberate: a mismatched domain produces links that can never attribute back.
It bites most often in development, where the link points at your production domain but the page is on localhost. Point the link at the current origin during development, or use cloud mode, which validates the domain on the dashboard instead.
Conversions are not attributed
- No widget mounted.
useReferralreads state from the API instance aReferralWidgetsets up. Without one somewhere in the tree, the hook warns andtrackConversiondoes nothing. - Tracking too early. Call
trackConversiononce the account or order exists, not when the form is submitted. - Storage cleared. The referral lives in a cookie and
localStorage. Private windows, cookie banners that wipe storage, and cross-device journeys all break the chain. - A different query parameter. Detection looks for
ref, thenreferralandinvite. Anything else needsqueryParamconfigured.
Events that fail to send are queued in localStorage and retried three times with backoff — check queuedEvents before concluding one was lost. See Conversion tracking.
Screenshot problems
Capture is slow
The full-page capture walks the entire DOM, which is noticeable on very large or image-heavy pages. If the pause between selecting an element and the form opening is too long, drop the full-page capture and keep the element-scoped one.
<FeedbackWidget
customize={{
form: { output: { excludeFullPageScreenshot: true } },
}}
/>Images are missing from screenshots
Cross-origin images render blank unless the server sends permissive CORS headers. Serve images from your own origin, or add Access-Control-Allow-Origin on the host serving them.
The widget selects itself
A custom trigger built with useFeedback becomes a selectable target unless you mark it as excluded. Apply avoidElementClassName to it and anything else that should stay unselectable.
const { toggleFeedback, avoidElementClassName } = useFeedback();
<button onClick={toggleFeedback} className={avoidElementClassName}>
Report a bug
</button>TypeScript errors on valid props
Passing mode to a widget narrows its props deliberately. In cloud mode customize, theme, onFormSubmit and referralLink are rejected, because the dashboard is the source of truth. Configure those in your project settings, or drop the widget-level mode prop if you need local overrides. See Modes.
Still stuck
Open an issue on GitHub with the console output and your provider configuration, or reach the team through support.