---
title: "Build an in-app feedback form in Next.js"
description: "Add a native feedback card to a Next.js App Router page, attach signed account context, submit a real response, and verify the workflow."
order: 30
type: "guide"
topic: "Frameworks"
tags:
  - "Next.js"
  - "React"
  - "in-app feedback"
  - "native form"
  - "respondent identity"
keywords:
  - "in app feedback tools"
  - "in app survey"
  - "Next.js feedback form"
updated: "2026-08-07"
---

An in-app feedback form should know where the person was, fit the product around it, and leave a response the team can act on. Sending someone to a separate survey page loses that moment. An iframe keeps the moment visible, but puts the controls behind a document boundary that complicates layout, focus, styling, and account context.

This guide builds a two-question feedback card as native React controls in a Next.js App Router page. Fillo keeps the form definition, validation, and accepted response; the host application keeps the route, session, layout, and after-submit behavior.

## What you will build

The example belongs on a settings route after someone has used the feature being evaluated. It asks for a rating and an optional note, then records the response in Fillo.

Keep the first version deliberately small:

- One rating that is quick to answer.
- One open question that asks what should change.
- Signed account identity when the route is behind login.
- A visible success state in the same card.

Fillo is not a replacement for product analytics. Keep behavioral events, funnels, cohorts, and session analysis in the system that already owns them. Use the form for the explanation a behavioral event cannot provide.

## Install the React renderer

```bash
pnpm add @usefillo/react
```

Import the optional default stylesheet from the root layout or another global CSS entry:

```tsx
// app/layout.tsx
import "@usefillo/react/styles.css";
```

Create a workspace publishable key, allow your application origin, and expose only that public capability to the browser:

In `.env.local`:

```dotenv
NEXT_PUBLIC_FILLO_KEY=pk_your_publishable_key
```

An `fsk_` workspace key must remain on a trusted server. The browser needs a `pk_` key only because this example syncs a code-defined form with the workspace.

## Add the feedback card

Create a Client Component. Keep the Fillo client outside the component so React does not recreate it on every render.

```tsx
// app/settings/FeedbackCard.tsx
"use client";

import { createClient, Fillo } from "@usefillo/react";

const publishableKey = process.env.NEXT_PUBLIC_FILLO_KEY;
const fillo = publishableKey ? createClient({ key: publishableKey }) : null;

type Respondent = {
  id: string;
  email?: string;
  name?: string;
  hash?: string;
};

export function FeedbackCard({ respondent }: { respondent?: Respondent }) {
  if (!fillo) {
    return <p role="status">Add NEXT_PUBLIC_FILLO_KEY to render feedback.</p>;
  }

  return (
    <section aria-labelledby="feedback-title">
      <h2 id="feedback-title">How did this settings page work for you?</h2>

      <Fillo.Form
        client={fillo}
        id="nextjs-settings-feedback"
        title="Settings feedback"
        respondent={respondent}
      >
        <Fillo.Rating id="score" label="Overall experience" max={5} required />
        <Fillo.LongText id="note" label="What should we fix?" />
      </Fillo.Form>
    </section>
  );
}
```

The field IDs are the durable contract. Labels can change later without changing how the response, export column, or webhook identifies `score` and `note`.

In a claimed workspace that permits publishable-key sync, the first run stages the code-defined form. Review and publish it before relying on the public flow. Later schema edits are staged again instead of silently changing the form respondents see.

## Attach account context safely

If the route is behind sign-in, pass the user ID your application already knows. That identity can group responses and spare the person from typing their email again.

Treat an unsigned browser value as context, not authorization: page code can claim any ID. When identity needs to be trusted, enable respondent verification and compute the HMAC on your server. Pass only the resulting hash into the Client Component.

```ts
// lib/fillo-identity.ts — server only
import { createHmac } from "node:crypto";

export function filloRespondentHash(userId: string): string {
  return createHmac("sha256", process.env.FILLO_IDENTITY_SECRET!)
    .update(userId)
    .digest("hex");
}
```

The identity secret must never use a `NEXT_PUBLIC_` name. See [respondents and identity](/docs/respondents) for verified-only submission handling and save-and-resume behavior.

## Keep the form inside the product layout

The renderer creates real labels, controls, errors, buttons, and focus targets in the page. Put the card wherever the question makes sense: after an import, beside a new feature, inside settings, or after a completed task.

The surrounding product remains responsible for:

- A visible heading when the renderer title is hidden.
- Enough room for errors and the success state.
- Keyboard and narrow-screen testing.
- Deciding whether the card closes, stays visible, or links to support after submission.
- Sending behavioral events to product analytics separately.

Use Fillo theme tokens, named slot classes, or headless composition to match the host design system. The form does not need to look like a separate survey product.

## Review the response, not just the submit callback

Run the complete path once before placing the card in front of users:

1. Publish the staged form.
2. Load the real signed-in route from an allowed origin.
3. Trigger the required-rating error and confirm focus reaches it.
4. Submit one response.
5. Find the rating, note, source, and respondent in the response workspace.
6. Confirm any webhook or integration receives the accepted response and handles possible retries idempotently.

A successful browser callback proves the request returned. The durable response and its delivery state prove the workflow can continue after the page closes.

## Start from the runnable example

Clone the [Next.js product-feedback starter](https://github.com/jacobfunch/fillo-nextjs-feedback-starter). It includes the App Router page, environment example, styles, and a clean-install verification checklist.

For a builder-managed version, open the [customer feedback template](/templates/customer-feedback-form). The [in-app feedback overview](/in-app-feedback) shows the same job with a live native preview and the boundary between structured feedback and product analytics.

## Related

- [Custom layout example](/examples#custom-layout): See native fields placed inside a product-owned layout.
- [Add a form to Next.js](/guides/nextjs-form)
- [The request lifecycle of a native product form](/guides/native-form-request-lifecycle)
- [Respondents and identity](/docs/respondents)
- [Style the React renderer](/docs/styling)
