Skip to content

Next.js App Router

Render thefaqapp content in a Next.js 15+ app with SSR, ISR, and useFaq hooks.

Updated 2026-05-19

The most common integration: a Next.js app rendering FAQ content on a public marketing or help surface.

bun add @faqapp/core @faqapp/react

Server-side render a list

// app/help/page.tsx
import { createFAQClient } from "@faqapp/core";

export const revalidate = 300; // ISR — refresh every 5 minutes

export default async function HelpPage() {
  const faq = createFAQClient({
    apiKey: process.env.FAQAPP_API_KEY!,
    organizationSlug: "acme"
  });

  const { data: questions } = await faq.questions.list({ limit: 50 });

  return (
    <main>
      <h1>Help</h1>
      {questions.map((q) => (
        <article key={q.id}>
          <h2>{q.title}</h2>
          <p>{q.answer}</p>
        </article>
      ))}
    </main>
  );
}

The SDK is dependency-free, so it ships fine to RSC. FAQAPP_API_KEY should be a read-scope key, server-side only.

Per-question dynamic route

// app/help/[slug]/page.tsx
import { createFAQClient } from "@faqapp/core";
import { notFound } from "next/navigation";

export async function generateStaticParams() {
  const faq = createFAQClient({
    apiKey: process.env.FAQAPP_API_KEY!,
    organizationSlug: "acme"
  });
  const { data } = await faq.questions.list({ limit: 100 });
  return data.map((q) => ({ slug: q.slug }));
}

export default async function QuestionPage({ params }) {
  const faq = createFAQClient({
    apiKey: process.env.FAQAPP_API_KEY!,
    organizationSlug: "acme"
  });

  try {
    const { data: q } = await faq.questions.get(params.slug);
    return (
      <article>
        <h1>{q.title}</h1>
        <p>{q.answer}</p>
      </article>
    );
  } catch (err) {
    if (err.code === "question_not_found") notFound();
    throw err;
  }
}

Client-side hooks (interactive surfaces)

For client-side search, expanding rows, AI ask, or user-specific filtering:

// app/help/SearchableList.tsx
"use client";
import { FAQClientProvider, useFaq } from "@faqapp/react";

export function SearchableList() {
  return (
    <FAQClientProvider
      organizationSlug="acme"
      apiKey={process.env.NEXT_PUBLIC_FAQAPP_READ_KEY!}
    >
      <Inner />
    </FAQClientProvider>
  );
}

function Inner() {
  const [q, setQ] = useState("");
  const { data } = useFaq().questions.list({ q, limit: 20 });
  return (
    <div>
      <input value={q} onChange={(e) => setQ(e.target.value)} />
      {data?.map((x) => <Row key={x.id} {...x} />)}
    </div>
  );
}

For the public client component, use a read-scope API key exposed as NEXT_PUBLIC_*. Don’t expose write or admin keys to the browser.

Revalidate on publish

Wire a webhook from thefaqapp to a Next.js Route Handler that calls revalidatePath:

// app/api/faqapp-webhook/route.ts
import { revalidatePath } from "next/cache";

export async function POST(req: Request) {
  const sig = req.headers.get("X-FAQApp-Signature");
  // Verify signature against your webhook secret
  // ...
  const event = await req.json();
  if (event.type === "question.published") {
    revalidatePath("/help");
    revalidatePath(`/help/${event.question.slug}`);
  }
  return new Response("ok");
}

Add the webhook URL in your dashboard under Settings → Webhooks. The signing secret lives in your env as FAQAPP_WEBHOOK_SECRET.