Notify guides
05 · Next.js App Router

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
// 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
// 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
// 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.