Mocha工程实践20 次阅读

给 Next.js 博客接入 Knocket 并用 Telegram 回复访客消息

#Knocket#Telegram#博客#Next.js

上一篇《腾讯云 Knocket 免费用:给博客加个聊天入口,手机就能回复》,先认识了网页聊天气泡、收件箱和 Telegram 各自的作用。这一篇把那条对话链路接到我的 Next.js 博客上。

这是 Knocket 系列的第二篇。我们从创建 Telegram 机器人开始,完成连接与激活、安装网页组件、排除后台和登录页,再用手机视口和正式站点验证消息收发。AI 助手和知识库留到第三篇。

目标很具体:访客看完文章或项目,可以直接在页面里提问;我在 Telegram 收到通知,对那条通知引用回复,回答回到访客的聊天窗口。博客上的开发咨询和文章反馈,就有了一个直接的沟通入口。

现在我的博客已经装好组件。公开页面右下角可以打开聊天窗口,后台和登录页不会出现。下面记录这次接入的完整过程。

正式博客首页右下角的 Knocket 聊天入口
图 1|正式站点实拍。右下角是已接入的 Knocket 聊天入口。

这篇记录的是 2026 年 10 月 14 日的实际接入过程。Knocket 设置页和博客验收图使用现场截图;BotFather、激活路径和 Telegram 回复操作的补图,标有“教程示意 · 非现场截图”。示意图里的账号样例和凭据不能直接用于连接。

一 开始前准备什么

需要准备一个能登录的 Knocket 账号、一个能正常使用的 Telegram 账号,以及博客的源码和部署权限。Telegram Bot 由自己的账号创建和管理。

我这次接入的是 Next.js 15.0.7、App Router 项目,已有 AppChrome 统一管理页头、页脚和后台页面的布局。下面的组件代码按这套结构说明,其他版本可以对照对应版本的 Next.js 文档调整。

连接过程会遇到几个容易混淆的值:

手机上可左右滑动查看这张表。
内容用在哪里本文怎样展示
Bot 用户名在 Telegram 中找到机器人,如 @your_blog_demo_bot可以展示,示例用户名需要自行替换
Bot Token授权 Knocket 操作你的机器人凭据必须保密,图中全部遮挡
Knocket identifierSDK 识别你的 Knocket 配置本来就会随网页脚本公开,代码中使用占位值
激活链接或命令连接向导绑定通知接收账号使用自己向导提供的内容,本文不展示真实绑定信息

Bot Token 和 identifier 是两回事。浏览器要加载的是带 identifier 的 SDK;Bot Token 填在 Knocket 的 Telegram 连接页面,不能写进前端组件、公开环境变量或博文代码。

这次接入没有给博客新增数据库表,也没有在博客服务器上单独部署 Telegram Bot 程序。

二 在 BotFather 创建机器人并拿到 Token

在 Telegram 中打开官方 @BotFather,发送:

/newbot

按提示填写机器人的显示名称和用户名。显示名称可以按用途起名,例如 Blog Support;用户名需要唯一,并以 bot 结尾,例如 your_blog_demo_bot。这个示例不保证可注册,实际要选一个尚未使用的用户名。

完成后,BotFather 会返回 Bot Token。创建命令、用户名规则和 Token 的用途可对照 Telegram 官方 BotFather 文档。

BotFather 创建机器人和获取 Token 的脱敏示意
图 2|教程示意,非 Telegram 现场截图。聊天文案是中文步骤说明,机器人名称为样例,Token 全部遮挡。

如果已经创建过机器人,先找 BotFather 的原创建消息,Token 就在那条消息里。如果 Token 已丢失或泄露,可以按官方说明使用 /token 生成新值,然后重新更新 Knocket 中的连接凭据。不要把生成新 Token 当成普通的“查看”操作。

我这次连接的机器人用户名是 @mocha_server_bot。读者需要使用自己创建的机器人和对应 Token。

三 在 Knocket 连接 Telegram 并完成激活

登录 Knocket 控制台,在左侧找到:

管理 → 集成 → Telegram 机器人
Knocket 集成页面的 Telegram 机器人入口
图 3|Knocket 集成页面实拍,选择 Telegram 机器人。

进入后,向导分成“输入 Bot Token”“激活”“完成”三个阶段。把刚才的真实 Token 粘贴到输入框,再点击“连接”。

Knocket Telegram 连接向导的 Token 输入步骤
图 4|连接向导实拍。输入框中的淡色内容是产品占位示例,不是真实 Token,也不能拿来连接。

填完 Token 后还要完成激活。按照当前向导提供的 Telegram 链接或命令,到 Telegram 中操作,再回到 Knocket 确认连接完成。不要只看第一步提交成功就结束,也不要套用其他账号的激活链接。

Telegram 机器人给个人发消息,需要用户先与机器人建立交互;Knocket 的激活步骤也要按自己的连接向导完成。Telegram 的这一前置条件可参见 官方机器人教程的发送消息说明。

Knocket Telegram 输入 Token 激活与完成的操作路径示意
图 5|操作路径示意。激活在 Telegram 中由我手动完成,没有记录当时的画面;这张图只说明顺序,不复原未知的激活码或链接。

接着打开左侧“管理 → 通知”,检查 Telegram 推送是否开启。为了从 Telegram 直接回复访客,我选择的是:

完整消息与引用回复

同页底部的 Telegram 机器人状态应显示“已连接”。

Telegram 推送开启并选中完整消息与引用回复
图 6|通知设置实拍。Telegram 推送开启,“完整消息与引用回复”已选中,下方显示机器人已连接。

“已连接”说明配置已完成,消息能否走完链路还要实测。后面会从博客里发一条消息,再从 Telegram 引用回复。

四 取到 SDK 嵌入代码

在 Knocket 控制台的“安装接入”页面,复制网页小组件的嵌入代码。官方 Web Widget 说明 给出的基本形式是:

<script
  src="https://trtc.io/knocket-sdk/sdk.js?identifier=YOUR_KNOCKET_IDENTIFIER"
  async
></script>

把 YOUR_KNOCKET_IDENTIFIER 换成自己控制台生成的 identifier。普通 HTML 页面可以把脚本放在 </body> 前;我的 Next.js 博客还需要处理共享布局和路由切换,所以把它封装成了一个客户端组件。

网页小组件的外观和欢迎语可以在 Knocket 控制台配置。我这次验收截图中的欢迎语仍是英文,读者可以按博客的语言和接待方式设置自己的文案。

五 接入 Next.js 并排除后台和登录页

这次改动集中在两个文件:新增 src/components/knocket-widget.tsx,然后在已有的 src/components/app-chrome.tsx 中挂载它。

延后加载并同步组件可见性

下面是接入组件的完整代码。代码中的 identifier 使用占位值,复制后要先替换。

"use client";

import { useEffect } from "react";
import { usePathname } from "next/navigation";
import Script from "next/script";

const SDK_URL =
  "https://trtc.io/knocket-sdk/sdk.js?identifier=YOUR_KNOCKET_IDENTIFIER";

type KnocketWindow = Window & {
  contactWidget?: {
    show?: () => void;
    hide?: () => void;
  };
};

function isExcludedPath(pathname: string) {
  return ["/admin", "/login"].some(
    (path) => pathname === path || pathname.startsWith(`${path}/`),
  );
}

function synchronizeVisibility() {
  const widget = (window as KnocketWindow).contactWidget;
  if (isExcludedPath(window.location.pathname)) {
    widget?.hide?.();
  } else {
    widget?.show?.();
  }
}

export function KnocketWidget() {
  const pathname = usePathname();
  const excluded = !pathname || isExcludedPath(pathname);

  useEffect(() => {
    synchronizeVisibility();
    return () => (window as KnocketWindow).contactWidget?.hide?.();
  }, [excluded]);

  return (
    <>
      <style id="knocket-route-style">{`
        #contact-widget-auto {
          --contact-widget-right: max(20px, env(safe-area-inset-right));
          --contact-widget-bottom: max(20px, env(safe-area-inset-bottom));
        }
        @media (max-width: 767px) {
          #contact-widget-auto {
            --contact-widget-right: max(12px, env(safe-area-inset-right));
            --contact-widget-bottom: max(12px, env(safe-area-inset-bottom));
          }
        }
        ${excluded ? "#contact-widget-auto { display: none !important; }" : ""}
      `}</style>
      {excluded ? null : (
        <Script
          id="knocket-sdk"
          src={SDK_URL}
          strategy="lazyOnload"
          onReady={synchronizeVisibility}
        />
      )}
    </>
  );
}

lazyOnload 把 SDK 的加载安排在页面资源加载完成后的浏览器空闲时间,适合聊天这类辅助功能。共享布局中使用 next/script,也方便在客户端导航时管理同一条脚本。加载策略和布局用法可以对照 Next.js 15 脚本加载指南。

路由排除要考虑两种情况。访客直接打开 /login 或 /admin/* 时,不渲染 SDK 脚本;SDK 已在公开页面加载后,切换到排除路由时,调用 hide() 并用样式隐藏宿主节点。单纯从 React 树移除 <Script>,无法保证 SDK 已经插入的页面节点也一起消失。

异步加载完成时,onReady 再读取当前的 window.location.pathname,根据此刻所在页面同步显示状态。这样,加载过程中发生路由变化,也不会沿用开始加载时的路径判断。

这里用到的 window.contactWidget、#contact-widget-auto 和 CSS 变量,来自本次实际使用的 Knocket SDK。后续如果 SDK 调整这些接口或节点,需要重新检查适配。路由排除负责隐藏聊天入口,后台的登录认证仍由博客原有逻辑处理。

在共享布局中挂载一次

我的博客已经有 AppChrome,因此把 <KnocketWidget /> 放在它的最外层,和原来的公开页面布局分支并列。页头、页脚、微信分享等原有组件继续保留。

下面是挂载位置的结构示意,注释处代表已有布局,不是要删掉它:

import { KnocketWidget } from "@/components/knocket-widget";

// AppChrome 内部已有 pathname、isAdmin、isLogin 等判断。
return (
  <>
    <KnocketWidget />
    {isAdmin || isLogin ? (
      children
    ) : (
      <div className="min-h-full flex flex-col">
        {/* 保留已有的分享组件和 Header */}
        <main className="flex-1">{children}</main>
        {/* 保留已有的 Footer */}
      </div>
    )}
  </>
);

组件自身负责排除路由,因此要保持在共享层中,让它能接收路由状态变化。没有 AppChrome 的项目,可以在根布局中挂载这个客户端组件;不要在每篇文章页面重复加入它。

接入后,我从“关于”页切到文章列表,页面中仍只有一条 Knocket SDK 脚本和一个聊天宿主节点。

公开页面切换后仍显示单个 Knocket 聊天入口
图 7|正式站点文章列表实拍。公开页面切换后,聊天入口正常显示。

检查登录页和后台入口

我分别直接访问了 /login 和 /admin/articles。登录页没有加载 Knocket SDK,也没有聊天宿主节点;未登录访问后台时,原有认证逻辑将页面重定向到登录页,同样没有加载聊天组件。

正式站点登录页没有显示 Knocket
图 8|正式站点登录页实拍,没有右下角聊天入口。后台入口验收使用未登录访问,排除规则覆盖 /admin 及其子路径。

六 检查手机端面板和输入框

组件用了 safe-area-inset-right 和 safe-area-inset-bottom 设置边距,以适应设备安全区。小屏边距设为 12px,桌面边距设为 20px。

这次我用浏览器模拟了两组手机视口:375 × 844 和 360 × 667 CSS 像素。检查了聊天入口能否点击、展开后的面板是否覆盖视口、输入框能否使用,以及页面有没有横向溢出。

两组视口都通过检查,聊天面板和输入框可见。这里验证的是网页聊天组件在手机尺寸下的响应式表现,没有接入原生 iOS 或 Android SDK,也还没有完成实体手机软键盘弹出后的测试。

手机视口中展开的 Knocket 聊天窗口
图 9|预览环境的手机视口实拍。聊天面板完整展开,底部输入区可见。

如果准备长期使用,建议再用自己的手机浏览器跑一遍:弹出键盘输入、发送消息、收起键盘、关闭面板,再重新打开。这能补上桌面视口模拟覆盖不到的交互。

七 构建部署后从正式站点验证双向消息

上线前,这两个接入文件通过了 ESLint 和 TypeScript 检查:

pnpm exec eslint src/components/knocket-widget.tsx src/components/app-chrome.tsx
pnpm exec tsc --noEmit --incremental false

随后完成了生产构建。项目的 build 脚本是 next build,常规执行方式是:

pnpm build

我的线上服务器内存较小,这次先取线上源码,在独立目录中只叠加两个接入文件进行本机构建,没有把工作区的其他改动一并发布。独立目录实际运行的是 node node_modules/next/dist/bin/next build,同样执行 Next.js 生产构建。构建期间使用现有配置,没有执行数据库迁移或种子脚本。

发布顺序是先在服务器上启动独立预检服务,检查新版本能否正常运行,再备份旧构建、源文件和 Nginx 配置,最后切换正式服务。旧静态资源也保留了,方便已经打开页面的访客继续请求缓存中的资源。切换后,正式首页和 15 个页面脚本资源检查通过。

消息测试从正式域名发起,按下面的顺序进行:

  1. 打开正式博客,点开右下角聊天入口,再点击 Start a Chat。
  2. 发送一条带有明确标记的测试消息,方便在 Telegram 中找到。
  3. 到本次连接的 Telegram Bot 聊天中,找到对应通知。
  4. 对那条通知选择“回复 / Reply”,输入测试回复并发送。
  5. 回到博客,检查刚才的访客会话是否显示回复。

我发送的内容是:

Knocket 正式站点验收:来自 https://www.mocha.wang/ 的手机端消息。
请引用回复「正式站点验收成功」。

Telegram 这一侧要使用引用回复。发送前,确认输入框上方已经出现对应的原消息引用。

在 Telegram 中对访客通知使用引用回复的示意
图 10|教程示意,非 Telegram 现场截图。用本次验收文本说明引用回复操作,通知排版为示例。

这次正式站点的 Telegram 通知已收到,引用回复的内容是“正式站点验收成功”。回到博客,聊天窗口里也出现了这句话。

正式博客聊天窗口收到 Telegram 回传的正式站点验收成功
图 11|正式站点实拍。上方是博客发出的测试消息,下方“正式站点验收成功”来自 Telegram 引用回复。

看到这条回信,才能确认网站发消息、Telegram 收通知、Telegram 回复、网站接收回信这几步都走通了。

八 这次遇到的问题和排查顺序

最开始,我用了 Knocket 收件箱里的“模拟会话”测试。消息能出现在收件箱中,Telegram 却没有收到通知。后来改用官方嵌入脚本创建的普通访客会话,Telegram 通知和引用回复都成功了,正式博客上线后也再次通过了验收。

这个结果只说明本次两种测试入口的表现不同,不能据此认定所有模拟会话都不会推送。排查时,我会先从实际安装了 SDK 的网页发送普通访客消息,避免仅凭后台的模拟会话判断 Telegram 是否连接成功。

现象先检查什么
页面没有聊天入口identifier 是否替换正确;SDK 是否被网络或浏览器扩展拦截;当前是否在排除路由;延后加载是否完成
收件箱有消息,Telegram 没通知是否完成激活;通知开关是否开启;是否使用真实网页访客会话测试
Telegram 有通知,网页没回信是否对对应通知使用了引用回复;是否选择了“完整消息与引用回复”
页面切换后出现重复入口是否在多个页面反复挂载组件;共享层是否只保留一份 SDK
后台仍出现聊天入口是否只判断了 /admin,遗漏子路径;是否处理了 SDK 已加载后的隐藏
桌面正常,手机输入困难再用实体手机检查软键盘、输入区位置和安全区边距

本次最终验收结果如下:

检查项结果
ESLint、TypeScript、生产构建通过
正式站点服务与页面脚本资源正常
公开页面导航一条 SDK 脚本、一个聊天宿主节点
登录页和未登录后台入口不加载 Knocket SDK 和组件
两组手机视口面板、输入框正常,无横向溢出
正式站点访客消息到 Telegram已收到
Telegram 引用回复回到博客已显示“正式站点验收成功”

下一篇 让聊天入口回答博客里的问题

人工咨询已经接通,接下来我想解决另一个具体问题:访客问“你能做哪些 AI 应用?”或者“这篇文章里提到的 Agent 工作流该怎么搭?”时,聊天窗口能否先根据博客内容给出有依据的回答,再把需要沟通的咨询交给我。

Knocket 官网提供了 AI Agent 和知识库入口,说明支持上传文档、FAQ,并在需要时转交人工。这个能力会作为 下一阶段的接入方向,本篇还没有配置或验收 AI 自动回复。

下一篇会围绕这份博客做知识库:先整理开发服务范围、个人介绍、常见咨询问题和公开文章,再检查模型接入、回答依据,以及 AI 与 Telegram 人工回复如何衔接。

我会重点测试几种问题:资料里有答案的,能不能答准;资料里没写的,会不会编造;涉及报价、排期和具体项目方案的,能不能及时交给人工。

先把“访客能联系到我”做好,下一篇再让它读懂博客。