关于 novu-inbox-integration
用于将 Novu 站内通知中心集成到 Web 应用的技能,涵盖 React、Next.js 和 JavaScript 包,以及展示、通知操作、个性化和实时更新配置。
主要功能
- 01通过 React 和 Next.js 集成 Inbox
- 02展示通知并支持已读和归档操作
- 03自定义主题、图标和外观
- 04个性化通知渲染与路由
- 05将通知分组到标签页
适用场景
- 01为 Web 应用添加通知中心
- 02使 Inbox 样式匹配产品主题
- 03按标签或严重级别筛选通知
- 04选择通知时跳转到相应页面
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ novuhq/ skills --skill novu-inbox-integration安装后在 Claude Code 中输入/novu-inbox-integration确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/branding-and-styling.mdheadless-inbox-examples.mdmulti-tenancy.mdnextjs-inbox-examples.mdpersonalization.mdreact-inbox-examples.mdsecurity.md详细介绍
收件箱集成
为你的 Web 应用添加应用内通知中心。Inbox 组件提供铃铛图标、通知动态、已读/归档管理、操作按钮和实时 WebSocket 更新,并支持主题化和个性化,以匹配你的产品。
软件包
| 软件包 | 用途 |
|---|---|
@novu/react | React 18/19 应用 |
@novu/nextjs | Next.js(App Router + Pages Router) |
@novu/js | 原生 JavaScript / 非 React 框架 |
React 快速入门
npm install @novu/react
import { Inbox } from "@novu/react";
function App() {
return (
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
subscriberHash="HMAC_HASH" // Required if HMAC encryption is enabled
/>
);
}
这会渲染带有未读数量的铃铛图标。点击后会打开一个包含通知动态的弹出层。
Next.js
npm install @novu/nextjs
App Router
// components/NotificationInbox.tsx
"use client";
import { Inbox } from "@novu/nextjs";
export function NotificationInbox() {
return (
<Inbox
applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!}
subscriberId="subscriber-123"
subscriberHash="HMAC_HASH"
/>
);
}
重要: Inbox 是客户端组件——在 Next.js App Router 中使用 "use client" 指令。
Pages Router
import { Inbox } from "@novu/nextjs";
export default function NotificationsPage() {
return (
<Inbox
applicationIdentifier={process.env.NEXT_PUBLIC_NOVU_APP_ID!}
subscriberId="subscriber-123"
subscriberHash="HMAC_HASH"
/>
);
}
可组合组件
<Inbox> 组件支持组合使用。传入子组件时,它会充当上下文提供程序,你可以通过基础组件组合 UI:
| 组件 | 用途 |
|---|---|
<Bell /> | 带有未读数量的铃铛图标 |
<Notifications /> | 通知动态(页眉 + 列表 + 页脚) |
<InboxContent /> | 与 <Notifications /> 相同,另含偏好设置页面 |
<Preferences /> | 独立的偏好设置面板 |
import { Inbox, Bell, Notifications, Preferences } from "@novu/react";
function App() {
return (
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
subscriberHash="HMAC_HASH"
>
<Bell />
<Notifications />
<Preferences />
</Inbox>
);
}
使用这些基础组件构建自定义弹出层、模态框、抽屉或全页通知体验。
Inbox 品牌化
Inbox 可通过 appearance 属性全面主题化。它支持四个键:
| 键 | 用途 |
|---|---|
baseTheme | 应用预定义主题(例如 dark) |
variables | 全局设计令牌(颜色、字体、圆角、严重程度颜色) |
elements | 单个元素的样式(样式对象、类名字符串或上下文回调) |
icons | 使用你自己的 React 组件替换内置图标 |
样式会自动注入到 <head>(若在 shadow DOM 内渲染,则注入到 shadow root)。同时提供 baseTheme 和 variables 时,以 variables 为准。
灵感来源:Inbox Playground 展示了 Notion 和 Reddit 等预设样式变体。
深色模式(及其他基础主题)
import { Inbox } from "@novu/react";
import { dark } from "@novu/react/themes";
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
appearance={{ baseTheme: dark }}
/>
全局变量
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
appearance={{
variables: {
colorPrimary: "#0081F1",
colorBackground: "#ffffff",
colorForeground: "#1A1523",
colorPrimaryForeground: "#ffffff",
colorSecondary: "#F1F0EF",
colorCounter: "#E5484D",
colorCounterForeground: "#ffffff",
colorNeutral: "#E0DEDC",
colorShadow: "rgba(0,0,0,0.08)",
fontSize: "14px",
borderRadius: "8px",
colorSeverityHigh: "#E5484D",
colorSeverityMedium: "#F76808",
colorSeverityLow: "#3E63DD",
},
}}
/>
元素级样式(Tailwind、CSS Modules、内联样式)
每个元素都接受类名字符串、样式对象,或用于运行时条件判断的函数 (context) => string。
import inboxStyles from "./inbox.module.css";
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
appearance={{
elements: {
bellIcon: ({ unreadCount }) =>
unreadCount.total > 10
? "p-4 bg-white rounded-full [--bell-gradient-end:var(--color-red-500)]"
: "p-4 bg-white rounded-full",
notification: ({ notification }) =>
notification.data?.priority === "high"
? "bg-red-50 ring-1 ring-red-300 rounded-lg"
: "bg-white rounded-lg shadow-sm hover:bg-gray-50",
notificationSubject: { fontWeight: 600 },
notificationBody: inboxStyles.body,
},
}}
/>
要查找元素键,请检查 DOM:任何以
nv-开头的类(在 DevTools 中 🔔 表情符号前可见)都对应appearance.elements中的一个键(去掉nv-前缀)。TS 自动补全会列出所有可用键。
自定义图标
通过从 appearance.icons 返回 React 组件来替换任何内置图标:
import { RiSettings3Fill, RiNotification3Fill } from "react-icons/ri";
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
appearance={{
icons: {
bell: () => <RiNotification3Fill />,
cogs: () => <RiSettings3Fill />,
},
}}
/>
常用图标键:bell、cogs、dots、arrowDown、arrowDropDown、arrowLeft、arrowRight、check、clock、trash、markAsRead、markAsUnread、markAsArchived、markAsUnarchived、email、sms、push、inApp、chat。要查找更多图标,请检查以 nv- 开头且包含 🖼️ 表情符号的类。
严重程度样式
通知和铃铛会根据严重程度(high、medium、low)设置样式。通过 variables 覆盖颜色:
严重程度仅是一个视觉调节项。工作流级别的
critical: true标志彼此独立——它会改变运行时投递行为(绕过偏好设置、跳过摘要),而不是 Inbox 样式。若critical工作流也需要在视觉上突出显示,应显式设置severity: 'high'。有关完整设计规则,请参阅design-workflow/references/severity-and-critical.md。
appearance: {
variables: {
colorSeverityHigh: "#E5484D",
colorSeverityMedium: "#F76808",
colorSeverityLow: "#3E63DD",
},
}
……也可以按元素设置:
appearance: {
elements: {
severityHigh__notificationBar: { backgroundColor: "red" },
severityHigh__bellContainer: "ring-2 ring-red-500",
severityGlowHigh__bellSeverityGlow: "bg-red-500",
},
}
默认情况下,铃铛会采用未读通知中最高严重程度对应的颜色。
响应式 Inbox
<Inbox
/* ... */
appearance={{ elements: { popoverContent: "novu-popover-content" } }}
/>
.novu-popover-content { max-width: 500px; }
@media (max-width: 768px) { .novu-popover-content { max-width: 350px; } }
@media (max-width: 480px) { .novu-popover-content { max-width: 250px; } }
完整的变量列表、严重程度元素键、动态回调签名以及 Notion/Reddit 风格预设,请参阅品牌化与样式参考。
个性化
渲染属性
覆盖通知的各个部分,同时保留周围的界面元素(操作按钮、悬停状态等):
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
renderBell={(unreadCount) => <MyBell count={unreadCount.total} />}
renderAvatar={(notification) => <Avatar src={notification.avatar} />}
renderSubject={(notification) => <strong>{notification.subject}</strong>}
renderBody={(notification) => <p>{notification.body}</p>}
renderDefaultActions={(notification) => <MyActions notification={notification} />}
renderCustomActions={(notification) => (
<PrimarySecondaryButtons notification={notification} />
)}
/>
仅当需要完全控制通知项时,才使用 renderNotification——你需要自行重新实现默认操作(标记为已读、归档、稍后提醒)。
<Inbox
/* ... */
renderNotification={(notification) => (
<div className="custom-row">
<h3>{notification.subject}</h3>
<p>{notification.body}</p>
</div>
)}
/>
条件显示
renderNotification 会接收完整的通知——可根据 tags、data、severity 或 workflow.identifier 进行分支处理:
renderNotification={(notification) => {
if (notification.severity === SeverityLevelEnum.HIGH) return <HighPriorityRow notification={notification} />;
if (notification.tags?.includes("billing")) return <BillingRow notification={notification} />;
if (notification.data?.priority === "high") return <UrgentRow notification={notification} />;
return <DefaultRow notification={notification} />;
}}
通知内容中的 HTML
若要在 subject / body 中渲染富 HTML:
- 在工作流的 In-App 步骤中停用 Disable content sanitization。
- 在渲染属性中使用
dangerouslySetInnerHTML进行渲染:
<Inbox
/* ... */
renderBody={(notification) => (
<div dangerouslySetInnerHTML={{ __html: notification.body }} />
)}
renderSubject={(notification) => (
<span dangerouslySetInnerHTML={{ __html: notification.subject }} />
)}
/>
仅当你完全控制触发负载时才启用此功能——原始 HTML 会带来 XSS 攻击面。
通知点击行为
将 Inbox 接入路由器。Novu 会使用工作流中定义的 redirect.url 调用 routerPush:
import { useRouter } from "next/navigation";
const router = useRouter();
<Inbox
/* ... */
routerPush={(path) => router.push(path)}
onNotificationClick={(notification) => track("inbox_notification_click", { id: notification.id })}
onPrimaryActionClick={(notification) => doSomething(notification.primaryAction)}
onSecondaryActionClick={(notification) => doSomethingElse(notification.secondaryAction)}
/>
适用于 React Router(useNavigate())、Remix(useNavigate())、Gatsby(navigate())以及任何自定义路由器。
完整的渲染属性签名、renderCustomActions 样式示例、使用 Radix / shadcn Drawer 组合弹出层,以及条件 UI 模式,请参阅个性化参考。
标签页
按标签、严重程度或 data 属性将通知分组到标签页中:
import { Inbox, SeverityLevelEnum } from "@novu/react";
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
tabs={[
{ label: "All", filter: { tags: [] } },
{ label: "Promotions", filter: { tags: ["promotions"] } },
{ label: "Security", filter: { tags: ["security", "alert"] } },
{ label: "Critical", filter: { severity: SeverityLevelEnum.HIGH } },
{ label: "High Priority", filter: { data: { priority: "high" } } },
{
label: "Billing",
filter: { tags: ["billing"], data: { entity: "invoice" } },
},
]}
/>
- 标签由工作流级别设置——在工作流编辑器中分配。多个标签使用
OR逻辑。 - 严重程度来自 In-App 步骤的严重程度设置(
HIGH、MEDIUM、LOW)。 data来自每个 In-App 步骤中定义的数据对象。
使用 useCounts hook显示每个标签页的未读数量。
使用上下文实现多租户
使用上下文将 Inbox 限定到租户、工作区或功能区域。Inbox 只会显示触发上下文与 Inbox 上下文完全匹配的通知。
1. 使用上下文触发工作流
await novu.trigger({
workflowId: "invoice-paid",
to: { subscriberId: "user-123" },
payload: { amount: "$250" },
context: {
tenant: {
id: "acme-corp",
data: { name: "Acme Corporation", plan: "enterprise" },
},
},
});
2. 向 Inbox 传入匹配的上下文
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="user-123"
subscriberHash="HMAC_HASH"
context={{
tenant: {
id: "acme-corp",
data: { name: "Acme Corporation", plan: "enterprise" },
},
}}
/>
3. 使用 contextHash 保护上下文
由于 context 在客户端设置,恶意用户可能会替换租户 ID。在服务器端生成规范化上下文的 HMAC 哈希:
import { createHmac } from "crypto";
import { canonicalize } from "@tufjs/canonical-json";
const context = {
tenant: { id: "acme-corp", data: { name: "Acme Corporation", plan: "enterprise" } },
};
const contextHash = createHmac("sha256", process.env.NOVU_SECRET_KEY!)
.update(canonicalize(context))
.digest("hex");
将其与 context 一同传入:
<Inbox
/* ... */
context={context}
contextHash={contextHash}
/>
上下文匹配规则
| 工作流上下文 | Inbox 上下文 | 是否显示? |
|---|---|---|
{ tenant: "acme" } | { tenant: "acme" } | ✅ |
{} | {} | ✅ |
{ tenant: "acme" } | {} | ❌ |
{} | { tenant: "acme" } | ❌ |
{ tenant: "acme" } | { tenant: "globex" } | ❌ |
Novu 会自动创建尚不存在的上下文。为防止覆盖,现有上下文数据不会自动更新。
完整设置说明、仪表板管理以及使用 {{context}} 渲染动态内容,请参阅多租户参考。
数据对象
每个 In-App 步骤都支持自定义数据对象——最多包含 10 个标量键值对(字符串、数字、布尔值、null;字符串长度 ≤ 256 个字符),在工作流编辑器中定义。值可以是静态的("status": "merged"),也可以是动态的("firstName": "{{subscriber.firstName}}")。
可在客户端通过 notification.data 访问,并用于渲染决策、条件样式和标签页筛选。
<Inbox
/* ... */
renderNotification={(notification) => (
<div>
<span>{notification.data?.emoji}</span>
<strong>{notification.data?.firstName}</strong>
<p>{notification.body}</p>
</div>
)}
/>
为数据对象指定全局类型,以启用自动补全:
declare global {
interface NotificationData {
reactionType?: string;
entityId?: string;
userName?: string;
}
}
不要在
data中存储机密——它会返回给客户端。切勿将整个触发负载展开到data中。
自定义弹出层
将通知动态流嵌入任意弹出层、抽屉或页面布局中。使用 <Bell />(或你自己的触发器)搭配 <Notifications /> 或 <InboxContent />:
import { Inbox, InboxContent, Bell } from "@novu/react";
import { Popover, PopoverTrigger, PopoverContent } from "@radix-ui/react-popover";
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
>
<Popover>
<PopoverTrigger>
<Bell />
</PopoverTrigger>
<PopoverContent className="h-[600px] w-[400px] p-0">
<InboxContent />
</PopoverContent>
</Popover>
</Inbox>
同样的模式也适用于 shadcn <Drawer>、Headless UI 或路由级页面(直接挂载 <InboxContent />,无需任何弹出层)。所有自定义属性(appearance、localization、tabs、routerPush、渲染属性)都会通过 <Inbox> 提供程序传递。
本地化
覆盖 Inbox 界面文本,适用于多语言应用或与产品语气保持一致:
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
localization={{
locale: "en-US",
"inbox.filters.labels.default": "Notifications",
"inbox.filters.dropdownOptions.unread": "Unread only",
"notifications.emptyNotice": "You're all caught up.",
"notifications.actions.readAll": "Mark all as read",
"notification.actions.archive.tooltip": "Move to archive",
"preferences.title": "Notification Preferences",
dynamic: {
"new-comment-on-post": "Post comments",
"new-follower-digest": "New Follower Updates",
},
}}
/>
- 本地化仅更改界面文本。要翻译通知内容,请使用工作流翻译。
- 使用
dynamic映射本地化 Preferences 界面中显示的工作流名称。 - 完整的键列表见
defaultLocalization.ts。
HMAC 身份验证
生产环境中必须启用,以防止订阅者身份冒用。完整指南请参阅 https://docs.novu.co/platform/inbox/prepare-for-production。
生成哈希(服务端)
import { createHmac } from "crypto";
const subscriberHash = createHmac("sha256", process.env.NOVU_SECRET_KEY!)
.update(subscriberId)
.digest("hex");
Python
import hmac, hashlib
subscriber_hash = hmac.new(
NOVU_SECRET_KEY.encode(),
subscriber_id.encode(),
hashlib.sha256,
).hexdigest()
传递给组件
<Inbox
applicationIdentifier="YOUR_NOVU_APP_ID"
subscriberId="subscriber-123"
subscriberHash={subscriberHash}
/>
如果还传入了 context,请生成 contextHash(参见使用 Context 的多租户)。
常见问题
applicationIdentifier与NOVU_SECRET_KEY不同——应用 ID 是可安全用于客户端的公开标识符。密钥仅限服务端使用。- 生产环境中必须使用 HMAC 哈希——没有它,任何人都可以通过猜测订阅者 ID 来冒充订阅者。
- Inbox 只显示来自包含
inApp步骤的工作流的通知——如果工作流不包含step.inApp(),就不会显示任何内容。 - 在 Next.js App Router 中必须使用
"use client"——Inbox 组件仅在客户端运行。 - 实时更新会自动进行——Inbox 内部使用 WebSocket。无需额外设置。
@novu/react与@novu/nextjs——Next.js 应用使用@novu/nextjs(可处理 SSR 边缘情况),其他 React 应用使用@novu/react。variables会覆盖baseTheme——当两者都在appearance中设置时,以 variables 为准。请有意在深色/浅色主题中设置 variables。- 元素回调返回字符串——
(context) => string返回的是类名,而非样式对象。要使用样式对象,请使用静态值。 - Context 过滤采用精确匹配——向 Inbox 传入
context={{}}会隐藏任何由非空 context 触发的通知,反之亦然。 - 不要在
notification.data中存储密钥——它会发送到客户端。 renderNotification会移除默认操作——如果要保留标记为已读/归档/延后等操作,请使用细粒度的渲染属性(renderSubject、renderBody、renderAvatar、renderDefaultActions、renderCustomActions)。- 渲染 HTML 需要完成两个步骤——在工作流中禁用清理,并且在渲染属性中使用
dangerouslySetInnerHTML。只做其中一步不会生效。
参考资料
- 品牌与样式——完整的外观 API:主题、变量、元素、图标、严重级别、动态回调
- 个性化——渲染属性、自定义弹出层(Radix、shadcn Drawer)、条件显示、点击处理器
- 使用 Context 的多租户——基于 Context 的隔离、保护 contextHash、动态模板
- React Inbox 示例
- Next.js Inbox 示例
- 无头 Inbox(Vanilla JS)
- 安全性(HMAC)