news 2026/9/15 17:15:42

在 React Router 7 框架模式中集成 Puck 可视化编辑器与 Puck AI:完整实战配方(react-router-ai)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
在 React Router 7 框架模式中集成 Puck 可视化编辑器与 Puck AI:完整实战配方(react-router-ai)

在 React Router 7 框架模式中集成 Puck 可视化编辑器与 Puck AI:完整实战配方(react-router-ai)

【免费下载链接】puckThe visual editor for React.项目地址: https://gitcode.com/GitHub_Trending/puc/puck

导读

本篇文章围绕 Puck 开源仓库中 recipes/react-router-ai/README.md 这份配方文档展开,讲解如何把可视化页面编辑器Puck与其 AI 副驾驶能力Puck AI集成进React Router 7 框架模式(Framework Mode)应用,实现对任意路由页面的可视化创建、AI 生成与发布。读完本文,你将掌握 Puck 的 Config / Editor / Render 三大核心概念、Puck AI 的 Assembly(组装)与 Design(设计)两种工作模式、/edit路由约定、puckHandler云端代理接入方式,以及生产部署前必须处理的鉴权、数据持久化等关键事项。

Puck 与 Puck AI 是什么

Puck 是面向 React 的开源可视化编辑器。它不是一套预置组件库,而是让你用自己的组件构建页面构建器(page builder)——你只需提供组件与字段配置,Puck 负责拖拽、编辑、布局等全部编辑器能力。

Puck AI在同样的原则之上更进一步:它可以根据自然语言提示,通过组装你已有的组件、或在需要时动态创建全新组件来生成页面。它既可以作为编辑器内的 copilot(AI 副驾驶),也可以通过服务端 API 以无头(headless)方式工作。

这份react-router-ai配方把 Puck 与 Puck AI 接入React Router 的框架模式,使你能够为应用中的任意路由创建和编辑页面。源码位于 recipes/react-router-ai,其中的package.json显示依赖 React Router^7.18.0、React^19.0.0,并通过react-router dev/react-router build运行,属于标准的 React Router 7 框架模式工程。

核心概念:Puck 的三大部分

Puck 可视化编辑器由三部分构成:Config(配置)Editor(编辑器)Renderer(渲染器)。理解这三者之间的关系,是使用本配方的前提。

Config:注册组件与字段

Config 注册用户可以在编辑器中用来构建页面的组件,以及他们可以编辑的字段。每个组件通常包含fields(字段定义)、render(渲染函数)和可选的defaultProps(默认属性)。原始配方中的示例配置如下:

const config = { components: { HeadingBlock: { fields: { title: { type: "text" }, }, render: ({ title }) => <h1>{title}</h1>, }, }, };

本仓库中对应的真实实现位于 recipes/react-router-ai/puck.config.tsx,它在示例基础上补充了类型定义与默认属性:

import type { Config, Data } from "@puckeditor/core"; export type Props = { HeadingBlock: { title: string }; }; export type UserData = Data<Props>; export const config: Config<Props> = { components: { HeadingBlock: { fields: { title: { type: "text" }, }, defaultProps: { title: "Heading", }, render: ({ title }) => ( <div style={{ padding: 64 }}> <h1>{title}</h1> </div> ), }, }, };

从源码结构可以看出:

  • fields.title声明text类型字段,编辑器会据此自动渲染输入控件;
  • defaultProps.title = "Heading"作为新插入组件时的默认值;
  • render接收组件 props 并返回 React 元素,这是页面渲染的唯一出口;
  • PropsUserData类型把「组件 props」与「页面数据」做了类型化约束,这正是把 Puck 接入 TypeScript 项目时的推荐做法。

Editor:渲染编辑器

<Puck>组件负责渲染编辑器。它接收 config(编辑器可用的组件集合)、data(要编辑的页面 JSON),并通过onPublish回调把页面数据导出。

<Puck config={config} // The components available to the editor data={data} // The page JSON to edit onPublish={(data) => { // Save data to your database }} />

配方中的编辑器实现位于 recipes/react-router-ai/app/routes/puck-splat.tsx 的Editor函数内。它使用useFetcher以 POST 方式把页面 JSON 提交给 action 完成保存:

<Puck plugins={plugins} config={configWithDesignedComponents} data={loaderData.data} onPublish={async (data) => { await fetcher.submit( { data: data as UserData }, { action: "", method: "post", encType: "application/json", } ); }} />

Renderer:渲染页面

<Render>组件负责渲染已保存的页面。它期望接收页面 JSON 和创建该页面时使用的 config。

<Render config={config} // The components used to create the page data={data} // The page JSON to render />

配方将渲染逻辑封装为可复用组件 recipes/react-router-ai/app/components/puck-render.tsx。注意它调用了withDynamicConfig——因为 AI 在 Design 模式下可能动态生成新组件,渲染已生成页面时必须把「设计出来的组件」合并进 config,页面才能正确还原:

import type { Data } from "@puckeditor/core"; import { Render } from "@puckeditor/core"; import { withDynamicConfig } from "@puckeditor/plugin-ai"; import { config } from "../../puck.config"; export function PuckRender({ data }: { data: Data }) { const configWithDesignedComponents = withDynamicConfig(config, data); return <Render config={configWithDesignedComponents} data={data} />; }

核心概念:Puck AI 的两大部分

Puck AI 作为 copilot 接入,由两部分组成:AI 插件(浏览器端)Cloud Client(服务端)

AI 插件(浏览器)

AI 插件 在编辑器中渲染聊天界面,并把每一条消息发送给你服务器上的 Cloud Client。

const aiPlugin = createAiPlugin(); function Editor() { return <Puck plugins={[aiPlugin]} config={config} data={data} />; }

配方中的插件配置(见 puck-splat.tsx)做得更细致——它开放了 Design/Assembly 模式切换,并默认使用 Design 模式,同时把 AI 插件排在侧边栏第一位:

const aiPlugin = createAiPlugin({ // Allow users to switch between design and assembly mode. designMode: { visible: true, }, // Select design mode by default. defaultMode: "design", }); // Place the ai plugin in the first position in the side nav. const plugins = [aiPlugin, blocksPlugin(), outlinePlugin()];

这里还同时启用了blocksPlugin()(组件列表插件)与outlinePlugin()(图层树插件),它们共同构成编辑器左侧的导航面板。

Cloud Client(服务器)

Cloud Client 提供把服务器连接到 Puck 云端的 API。本配方使用其中的puckHandlerAPI:它接收每一条聊天消息,转发给 Puck 云端,并把响应流式回传给浏览器中的 AI 插件。

const options = { ai: { context: "We are Google. You create Google landing pages.", }, }; export function loader(args: LoaderFunctionArgs) { return puckHandler(args.request, options); } export function action(args: ActionFunctionArgs) { return puckHandler(args.request, options); }

在 React Router 框架模式中,loader处理 GET 请求、action处理 POST 请求——AI 插件的通信既会用到 loader 也会用到 action,因此两者都要委托给puckHandler。配方中的完整实现见 recipes/react-router-ai/app/routes/api.puck.ts,它对 AI 行为做了更完整的配置:

import type { ActionFunctionArgs, LoaderFunctionArgs } from "react-router"; import type { PuckCloudOptions } from "@puckeditor/cloud-client"; import { puckHandler } from "@puckeditor/cloud-client"; const options: PuckCloudOptions = { ai: { // Replace with your business context context: "We are Google. You create Google landing pages.", designMode: { // Allow AI to generate new components using "design mode" allowed: true, // Constrain component generation, replace with your own instructions instructions: ` #### Color Palette Always use the following colors: * Primary: \`#1976d2\` * Secondary: \`#9c27b0\` `, }, }, }; export async function loader(args: LoaderFunctionArgs) { return puckHandler(args.request, options); } export async function action(args: ActionFunctionArgs) { return puckHandler(args.request, options); }

其中:

  • ai.context:业务上下文提示词,决定 AI 生成内容的方向(示例为 Google 落地页);
  • ai.designMode.allowed:是否允许 AI 动态生成新组件;
  • ai.designMode.instructions:对 AI 生成组件的约束指令,示例中约束了品牌色板,你可以替换为自己的设计规范(字体、间距、语气等)。

Puck AI 的两种模式

Puck AI 有两种构建页面的方式:

  • Assembly mode(组装模式):仅使用你 config 中已有的组件来构建页面,不会创建新组件;
  • Design mode(设计模式):在需要时可以生成全新组件。

本配方默认开启Design modeallowed: truedefaultMode: "design"),用户在编辑器中还可以通过插件上的designMode.visible: true随时切换两种模式。

运行配方

1. 添加 Puck API Key

首先创建账户并在 Puck 云端 生成 API Key,然后把它写入.env.local文件:

PUCK_API_KEY=your-api-key

PUCK_API_KEY由服务器端的 Cloud Client 读取,用于与 Puck 云端通信;该密钥只应存在于服务端环境,绝不能暴露在浏览器端。

2. 启动开发服务器

npm run dev

开发服务器启动后:

  • 访问 http://localhost:5173 查看首页;
  • 访问 http://localhost:5173/edit 使用 Puck 编辑首页。

3. 用 Puck AI 创建页面

进入 http://localhost:5173/edit,点击左侧边栏的AI按钮,输入提示词并按回车,AI 即会在编辑器中流式生成页面。

4. 发布页面

页面完成后,点击编辑器顶部的Publish保存结果,然后访问 http://localhost:5173 查看已发布的页面。

你还可以通过访问任意路径的/your/path/edit来创建新页面并发布,发布后/your/path路由就会渲染该页面——这正是「为任意路由创建页面」这一配方核心能力的体现。

工作原理:从 /edit 到发布

本配方的核心机制围绕 URL 中的/edit后缀展开:

  1. 当 URL 以/edit结尾时,recipes/react-router-ai/app/lib/resolve-puck-path.server.ts 中的resolvePuckPath返回被编辑页面的路径;
  2. recipes/react-router-ai/app/routes/puck-splat.tsx 的 loader 加载已保存的页面,若路径是全新的则返回一个空页面外壳;
  3. 点击Publish会把页面数据发送到puck-splat.tsx的 action;
  4. action 把 JSON 写入 recipes/react-router-ai/database.json;
  5. 该路由随后加载同一份数据,并用<Render>(经PuckRender封装)渲染发布结果。

resolvePuckPath 的解析逻辑

resolvePuckPath的源码实现值得细看:

export function resolvePuckPath( path = "", // `base` can be any valid origin, it is required for the URL constructor so // we can return a pathname - you can change this if you want, but it isn't // important base = "https://placeholder.com/" ) { const url = new URL(path, base); const segments = url.pathname.split("/"); const isEditorRoute = segments.at(-1) === "edit"; const pathname = isEditorRoute ? segments.slice(0, -1).join("/") : url.pathname; return { isEditorRoute, path: new URL(pathname, base).pathname, }; }

它把路径按/切分,判断最后一段是否为edit

  • 若是,则isEditorRoute = true,去掉最后一段得到被编辑页面的真实路径;
  • 若否,则按普通页面处理。

例如/docs/intro/edit会解析为「编辑路径/docs/intro」,而/docs/intro会解析为「渲染路径/docs/intro」。base参数只是为了满足URL构造函数的需要而提供的占位 origin,不影响实际路径结果。

loader:加载或初始化页面

puck-splat.tsx的 loader 完整逻辑如下:

export async function loader({ params }: Route.LoaderArgs) { const pathname = params["*"] ?? "/"; const { isEditorRoute, path } = resolvePuckPath(pathname); let page = await getPage(path); // Throw a 404 if we're not rendering the editor and data for the page does not exist if (!isEditorRoute && !page) { throw new Response("Not Found", { status: 404 }); } // Empty shell for new pages if (isEditorRoute && !page) { page = { content: [], root: { props: { title: "", }, }, }; } return { isEditorRoute, path, data: page, }; }

要点:

  • 非编辑路由且页面数据不存在时,直接抛出 404;
  • 编辑路由且页面数据不存在时,返回一个空页面外壳(content: []表示没有内容块),用户可以从零开始构建;
  • loader 同时把isEditorRoutepathdata返回给组件层,由默认导出组件决定渲染编辑器还是发布后的页面。

action:保存页面

export async function action({ params, request }: Route.ActionArgs) { const pathname = params["*"] ?? "/"; const { path } = resolvePuckPath(pathname); const body = (await request.json()) as { data: Data }; await savePage(path, body.data); }

action 从请求体中读取{ data }(编辑器onPublishapplication/json提交的内容),调用savePage写入数据存储。编辑器端通过useFetcherfetcher.submit触发这次 POST,且action: ""表示提交给当前路由自身的 action。

数据读写:pages.server.ts

recipes/react-router-ai/app/lib/pages.server.ts 以本地 JSON 文件充当简易数据库:

import path from "path"; import { fileURLToPath } from "url"; import fs from "fs/promises"; import type { Data } from "@puckeditor/core"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const databasePath = path.join(__dirname, "..", "..", "database.json"); export async function getPage(path: string) { const pages = await readDatabase(); return pages[path]; } export async function savePage(path: string, data: Data) { const pages = await readDatabase(); pages[path] = data; await fs.writeFile(databasePath, JSON.stringify(pages), { encoding: "utf8" }); } async function readDatabase() { try { const file = await fs.readFile(databasePath, "utf8"); return JSON.parse(file) as Record<string, Data>; } catch (error: unknown) { console.error(error); return {}; } }

从源码可以确认:

  • 数据模型是「路径 → 页面 JSON」的映射,如 database.json 中的"/"键存储首页数据;
  • readDatabase在文件缺失或解析失败时返回空对象(并打印错误),保证首次运行不会崩溃;
  • savePage每次全量写回 JSON 文件——这适合本地演示,但不适合多实例或 serverless 环境(见后文「生产部署前」)。

路由注册

recipes/react-router-ai/app/routes.ts 使用 React Router 7 的声明式路由 API 注册三条路由:

import type { RouteConfig } from "@react-router/dev/routes"; import { route, index } from "@react-router/dev/routes"; export default [ index("routes/_index.tsx"), route("api/puck/*", "routes/api.puck.ts"), route("*", "routes/puck-splat.tsx"), ] satisfies RouteConfig;
  • index("routes/_index.tsx"):首页路由,直接渲染已发布的首页;
  • route("api/puck/*", "routes/api.puck.ts"):Puck AI 的 API 端点,处理来自 AI 插件的请求;
  • route("*", "routes/puck-splat.tsx"):兜底(catch-all)路由,负责所有其他路径的编辑与渲染。

其中首页路由 recipes/react-router-ai/app/routes/_index.tsx 与puck-splat的逻辑同构:调用resolvePuckPath("/")解析首页路径,通过getPage读取页面数据,不存在则 404,然后交给PuckRender渲染。

文件职责速查表

下表汇总了本配方中实现上述流程的关键文件及其职责(见原始文档 README):

文件用途
puck.config.tsx定义可供 Puck 与 Assembly 模式使用的组件、字段与默认属性。需要加入你自己的组件时修改此文件。
app/routes.ts注册首页、Puck AI API 与兜底页面路由。
app/routes/puck-splat.tsx加载并保存页面数据,然后渲染编辑器或已发布页面。
app/routes/api.puck.ts处理来自 AI 插件的请求并配置 AI 生成行为。
app/routes/_index.tsx加载并渲染首页。
app/lib/resolve-puck-path.server.ts/editURL 映射为被编辑页面的路径。
app/lib/pages.server.tsdatabase.json中读写页面数据。可替换为你自己的数据获取与保存逻辑。
app/components/puck-render.tsx使用<Render>渲染已保存的页面数据。
database.json充当本地数据库。可替换为你自己的数据库方案。

生产部署前必须完成的检查清单

在把本配方部署到生产环境之前,请务必确认以下几点(原始文档列出的五项硬性要求):

  1. 保护编辑器与 API。/edit路由、发布 action 与/api/puck路由默认都是公开的。必须添加认证(authentication)、授权(authorization)与限流(rate limiting),以保护页面数据与 AI 用量。
  2. 加入你自己的组件库。puck.config.tsx中的示例组件HeadingBlock替换为用户真正需要的组件与字段。这是把「示例配方」变成「业务页面构建器」的关键一步。
  3. 设置你的业务上下文。将 app/routes/api.puck.ts 中示例的 Google 上下文替换为关于你的产品、目标受众与内容规则的清晰描述。上下文提示词直接决定 AI 生成页面的质量与一致性。
  4. 使用真正的数据库。替换database.jsonapp/lib/pages.server.ts中的函数。本地文件在多服务器实例或 serverless 部署中并不可靠。
  5. 选择合适的部署策略。本配方使用服务端渲染(SSR)、loader 与 action,因此要部署到兼容 React Router 的服务器运行时。从 react-router.config.ts 可以看到配方显式设置了ssr: true,且 package.json 提供了react-router buildreact-router-serve ./build/server/index.js两个生产构建/运行脚本。

总结

react-router-ai配方展示了一条完整的链路:React Router 7 框架模式的路由与 loader/action承接页面存取,Puck 的 Config/Editor/Render提供可视化编辑能力,Puck AI 的插件与 Cloud Client提供基于自然语言的页面生成能力。你可以沿着这条链路,把任意 React 应用升级为具备 AI 能力的可视化建站系统——唯一需要替换的,就是示例组件与示例数据库。

【免费下载链接】puckThe visual editor for React.项目地址: https://gitcode.com/GitHub_Trending/puc/puck

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 17:13:45

RoboCup仿真2D从源码到上场:agent2d编译与连接全攻略

第一次把 Agent2D 的源码编译出可执行文件、然后看着 11 个自带编号的小球员在同一台机器上连进 rcssserver 时&#xff0c;我才真正理解 RoboCup 仿真2D 的“球队”不过是一堆遵守同一套通信协议的进程。很多新手卡在这个环节&#xff1a;教程只说到“下载源码”“编译”&…

作者头像 李华
网站建设 2026/9/15 17:13:10

资金核对平台进化史:从Excel手工对账到智能实时对账系统

资金核对平台的发展历程&#xff1a;从Excel大战到智能对账&#xff0c;我亲历的四个时代在支付公司干过资金链路的人&#xff0c;大概都记得那种深夜被财务叫醒的恐惧——银行流水和系统账对不上&#xff0c;差一分钱&#xff0c;所有人都别想睡。我做资金核对平台这门生意差不…

作者头像 李华
网站建设 2026/9/15 17:12:15

基于Java+springboot的驾校管理系统-附源码

温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;本人主页置顶文章(点我)开头有 CSDN 平台…

作者头像 李华
网站建设 2026/9/15 17:11:28

基于Spring Boot的企业车辆管理系统源码解析与实战部署

简介&#xff1a;基于Spring Boot框架的企业车辆管理系统&#xff0c;面向Java开发者与高校计算机专业学生&#xff0c;适用于课程设计、毕业设计或企业车辆信息化管理场景。系统涵盖管理员、驾驶员、用户三类角色&#xff0c;核心模块包括车辆登记、车辆运营、通用接口和配置管…

作者头像 李华