Notify in the Next.js App Router.
A route handler mints tokens from your session; a client component renders the inbox inside your Server Component layout.
npm install @productcraft/notify @productcraft/notify-react. Keep PRODUCTCRAFT_API_KEY in a server-only variable: never a NEXT_PUBLIC_ one.
1
The token route
// app/api/notify-token/route.ts
import { Notify } from "@productcraft/notify";
import { currentUser } from "@/lib/auth"; // your session helper
const notify = new Notify(); // server-only env: PRODUCTCRAFT_API_KEY, PRODUCTCRAFT_WORKSPACE_ID
export async function GET() {
const user = await currentUser();
if (!user) return new Response(null, { status: 401 });
const { token } = await notify.recipientTokens.mint({ recipient: user.id, ttl_seconds: 3600 });
return new Response(token, { headers: { "cache-control": "no-store", "content-type": "text/plain" } });
}2
A client component
Every export of @productcraft/notify-react is a client module. Put the provider in a file that starts with "use client", pass onNavigate so clicking an item uses the router, and key the provider by user id so signing in as someone else starts clean.
// components/notifications.tsx
"use client";
import { useRouter } from "next/navigation";
import { NotifyInbox, NotifyProvider } from "@productcraft/notify-react";
import "@productcraft/notify-react/styles.css";
export function Notifications({ userId, locale }: { userId: string; locale: string }) {
const router = useRouter();
return (
<NotifyProvider key={userId} getToken={() => fetch("/api/notify-token").then((r) => r.text())} locale={locale}>
{/* Client-side navigation instead of a full page load. */}
<NotifyInbox onNavigate={(url) => router.push(url)} />
</NotifyProvider>
);
}3
Render it from a layout
// app/(app)/layout.tsx (a Server Component)
import type { ReactNode } from "react";
import { currentUser } from "@/lib/auth";
import { Notifications } from "@/components/notifications";
export default async function AppLayout({ children }: { children: ReactNode }) {
const user = await currentUser();
return (
<>
<nav>{user ? <Notifications userId={user.id} locale="en" /> : null}</nav>
{children}
</>
);
}The server render shows the bell without a count; the client fills it in. The component’s default locale is en rather than the browser’s, so the server and first client render match: pass the user’s locale from your app.