Install
Add the package, then render <FeatureFast /> once near the root of your app. Pass customerId and a customerHash you compute on your server (see below).
Install the widget in an authenticated app, with identity verified server-side.
Add the package, then render <FeatureFast /> once near the root of your app. Pass customerId and a customerHash you compute on your server (see below).
npm install @featurefast/react
import { FeatureFast } from "@featurefast/react";
export function App({ user, customerHash }: {
user: { id: string; email?: string };
customerHash: string;
}) {
return (
<FeatureFast
projectId="<your projectId>"
customerId={user.id}
customerHash={customerHash}
customerEmail={user.email}
/>
);
}Your projectIdis on the install page in your dashboard. It's public and safe to ship in client-side code.
FeatureFast uses React hooks, so wrap it in a "use client" module before importing it from the server-rendered RootLayout. Compute the hash on the server and render the widget only for signed-in users.
// app/FeatureFastWidget.tsx
"use client";
export { FeatureFast } from "@featurefast/react";
// app/layout.tsx
import { createHmac } from "node:crypto";
import { FeatureFast } from "./FeatureFastWidget";
import { auth } from "@/lib/auth";
export default async function RootLayout({
children,
}: { children: React.ReactNode }) {
const user = await auth();
const customerHash = user
? createHmac("sha256", process.env.FEATUREFAST_SECRET_KEY!)
.update(user.id)
.digest("hex")
: "";
return (
<html lang="en">
<body>
{children}
{user ? (
<FeatureFast
projectId="<your projectId>"
customerId={user.id}
customerHash={customerHash}
customerEmail={user.email}
/>
) : null}
</body>
</html>
);
}customerHash is HMAC-SHA256(secretKey, customerId), hex-encoded. Compute it on your serverand pass it to the client. Never expose your project's secret key (ff_sk_…) in the browser — it lives only in your server environment as FEATUREFAST_SECRET_KEY.
import { createHmac } from "node:crypto";
export function featureFastHash(customerId: string): string {
const secret = process.env.FEATUREFAST_SECRET_KEY;
if (!secret) throw new Error("FEATUREFAST_SECRET_KEY not configured");
return createHmac("sha256", secret).update(customerId).digest("hex");
}| Prop | Type | Required | Notes |
|---|---|---|---|
projectId | string | yes | From your FeatureFast dashboard. |
customerId | string | yes | Your stable user id. |
customerHash | string | yes | HMAC-SHA256 of customerId, hex. |
customerEmail | string | no | Optional, surfaced to the founder. |
The component throws synchronously if any required prop is missing.
Content-Security-Policy is the #1 blocker
Content-Security-Policy header (or <meta http-equiv>), you must allow-list FeatureFast in two directives or the browser silently blocks the widget — button.jsfails with “Provisional headers are shown” and the widget never appears.script-src https://cdn.featurefast.ai — loads the widget runtime.connect-src https://<your-deployment>.convex.site — submits prompts to your FeatureFast (Convex) Site URL.The host ends in .convex.site (not .convex.cloud). Find your Site URL in the dashboard, or run npx convex env get CONVEX_SITE_URL. These directives are additive — merge them into your existing CSP rather than replacing it.
script-src 'self' https://cdn.featurefast.ai; connect-src 'self' https://<your-deployment>.convex.site;
// next.config.js
const csp = [
"script-src 'self' https://cdn.featurefast.ai",
"connect-src 'self' https://<your-deployment>.convex.site",
].join("; ");
module.exports = {
async headers() {
return [
{
source: "/:path*",
headers: [{ key: "Content-Security-Policy", value: csp }],
},
];
},
};