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.

components/Feedback.tsx
"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:

Content-Security-Policy
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. useReferral reads state from the API instance a ReferralWidget sets up. Without one somewhere in the tree, the hook warns and trackConversion does nothing.
  • Tracking too early. Call trackConversion once 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, then referral and invite. Anything else needs queryParam configured.

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.

Element screenshot only
<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.

Excluding your own UI
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.