ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用 Puck 为 Next.js 任意路由搭建可视化编辑器:App Router Recipe 完整实战解析

用 Puck 为 Next.js 任意路由搭建可视化编辑器:App Router Recipe 完整实战解析 用 Puck 为 Next.js 任意路由搭建可视化编辑器App Router Recipe 完整实战解析【免费下载链接】puckThe visual editor for React.项目地址: https://gitcode.com/GitHub_Trending/puc/puckPuck 是开源的可视化编辑器Visual Editor for React本篇文章以仓库中的nextrecipe即 apps/demo/README.md 所描述的集成方案为主线完整讲解如何在 Next.js App Router 应用中为任意路由包括尚不存在的页面路径提供可视化创作与发布能力。读完本文你将掌握 Puck 的 Config / 编辑器 / 渲染器三大核心要素、/edit魔法路由的重写原理、发布数据流的完整链路以及从本地 JSON 数据库过渡到生产环境的注意事项。这个 Recipe 演示了什么nextrecipe 展示了使用 Puck 为 Next 应用中的任意路由提供创作工具的最强大方式之一。它的核心演示点集中在三个方面Next.js App Router 实现基于 Next.js App Router 的文件约定组织编辑端与渲染端代码JSON 数据库实现 HTTP API用本地database.json文件充当数据库并通过 HTTP API 保存页面数据无需任何外部依赖即可跑通全流程Catch-all 路由通过[...puckPath]捕获任意路径使平台上任何路由都能进入 Puck 编辑器进行创作与发布。仓库中recipes/next/目录就是该 recipe 的可直接运行实现其 READMErecipes/next/README.md提供了更完整的配套说明而apps/demo/则是承载该 recipe 的更大型演示应用完整组件库与页面样例两者配合阅读效果最佳。快速上手三分钟跑起来原文档给出的启动路径非常简单先通过官方脚手架生成项目npx create-puck-app my-app运行生成器后在提示符处输入next选择 next 模板仓库中 packages/create-puck-app/templates/next/package.json.hbs 即为该模板的依赖清单即可得到一个预置好的 Puck Next.js 集成项目。接着启动开发服务器原文档使用 yarn仓库中 recipes/next/package.json 与 apps/demo/package.json 也均定义了dev脚本yarn dev然后访问首页即可看到渲染效果访问编辑页则可进入 Puck 编辑器首页https://localhost:3000编辑首页https://localhost:3000/edit这个方案最妙的地方在于你可以对应用中的任何路由执行同样的操作即使该页面还不存在。例如访问https://localhost:3000/hello/world会得到一个 404但只需访问https://localhost:3000/hello/world/edit就能打开该路径的编辑器创作并点击发布后再回到原来的 URL页面就会被 Puck 渲染出来。这意味着「创建新页面」不再需要改代码编辑者自己就能完成。理解 Puck 的三大核心要素在深入代码之前先理解 Puck 与 Next.js 集成的三个核心部分这是理解整个 recipe 的前提。Config注册组件与字段Config 注册了用户可以在编辑器中用来搭建页面的组件以及每个组件可编辑的字段。一个最小配置形如const config { components: { HeadingBlock: { fields: { title: { type: text }, }, render: ({ title }) h1{title}/h1, }, }, };fields定义了组件在右侧面板中可编辑的属性如这里的title文本字段render定义了组件如何被渲染。编辑器Puck组件Puck组件渲染完整的可视化编辑器界面。它接收 Config决定编辑器中可用的组件、初始页面数据用于编辑已有页面并通过回调把发布后的页面 JSON 交还给你Puck config{config} // The components available to the editor data{data} // The page JSON to edit onPublish{(data) { // Save data to your database }} /渲染器Render组件Render组件负责渲染最终页面。它只需要页面 JSON 和创建该页面时使用的 ConfigRender config{config} // The components used to create the page data{data} // The page JSON to render /编辑器与渲染器共用同一份 Config从而保证「所见即所得」——编辑器里看到的内容结构与渲染结果严格一致。架构剖析/edit魔法与 Catch-all 路由整个 recipe 的精髓在于两个 Catch-all 路由加上一层 URL 重写构成了「任意路径都能编辑、任意路径都能渲染」的能力。路由重写层proxy.ts从源码结构看URL 末尾的/edit并不会真正对应一个目录而是由proxy.ts即 recipes/next/proxy.ts在请求进入时动态改写实现的核心逻辑如下export async function proxy(req: NextRequest) { const res NextResponse.next({ request: req }); if (req.method GET) { // Rewrite routes that match /[...puckPath]/edit to /puck/[...puckPath] if (req.nextUrl.pathname.endsWith(/edit)) { const pathWithoutEdit req.nextUrl.pathname.slice( 0, req.nextUrl.pathname.length - 5 ); const pathWithEditPrefix /puck${pathWithoutEdit}; return NextResponse.rewrite(new URL(pathWithEditPrefix, req.url)); } // Disable /puck/[...puckPath] if (req.nextUrl.pathname.startsWith(/puck)) { return NextResponse.redirect(new URL(/, req.url)); } } return res; }这段代码完成了两件事重写rewrite所有 GET 请求中路径以/edit结尾的去掉末尾的/edit后加上/puck前缀改写为/puck/[...puckPath]。例如/hello/world/edit→/puck/hello/world屏蔽redirect直接访问/puck/...会被重定向回首页确保编辑器入口只能通过/edit到达。正是这层改写让「任何路由 /edit」都能拉起编辑器而无需为每个页面单独编写编辑路由。编辑端app/puck/[...puckPath]/puck/[...puckPath]下的服务端组件recipes/next/app/puck/[...puckPath]/page.tsx负责为编辑器加载页面数据通过getPage(path)读取已保存的页面 JSON若路径尚无数据新页面则回退到EMPTY_PAGE_DATA一个只有空content和root的空数据结构通过generateMetadata动态生成编辑页标题Puck: path导出dynamic force-dynamic保证编辑页始终动态渲染、永远读取最新数据。对应的客户端组件recipes/next/app/puck/[...puckPath]/client.tsx则渲染Puck编辑器并处理发布逻辑use client; import type { Data } from puckeditor/core; import { Puck } from puckeditor/core; import config from ../../../puck.config; export function Client({ path, data }: { path: string; data: Data }) { return ( Puck config{config} data{data} onPublish{async (data) { await fetch(/puck/api, { method: post, body: JSON.stringify({ data, path }), }); }} / ); }注意这里的发布动作点击Publish后页面 JSON 与当前路径一起 POST 到/puck/api端点由服务端负责持久化。渲染端app/[...puckPath]面向访客的渲染路由recipes/next/app/[...puckPath]/page.tsx同样是一个 Catch-all调用getPage(path)查询数据库查不到数据时返回notFound()即 404查到数据时用Render渲染页面导出dynamic force-static让已发布的页面被静态化缓存后续访问直接命中缓存。对应的渲染客户端组件recipes/next/app/[...puckPath]/client.tsx非常简洁就是把数据和 Config 交给Renderuse client; import type { Data } from puckeditor/core; import { Render } from puckeditor/core; import config from ../../puck.config; export function Client({ data }: { data: Data }) { return Render config{config} data{data} /; }从源码注释见 recipes/next/app/[...puckPath]/page.tsx可以看出设计意图编辑端/puck路由保持动态而公开页面可以静态渲染——两者互不干扰兼顾编辑实时性与访客访问性能。发布数据流从编辑器到 JSON 数据库点击 Publish 后数据经历了「客户端收集 → HTTP API 写入 → 缓存失效 → 静态页重建」的完整链路1. 客户端提交编辑器客户端把{ data, path }POST 到/puck/api见上文 client.tsx。2. 服务端写入与缓存失效API 路由recipes/next/app/puck/api/route.ts的完整实现如下import { revalidatePath } from next/cache; import { NextResponse } from next/server; import fs from fs; export async function POST(request: Request) { const payload await request.json(); const existingData JSON.parse( fs.existsSync(database.json) ? fs.readFileSync(database.json, utf-8) : {} ); const updatedData { ...existingData, [payload.path]: payload.data, }; fs.writeFileSync(database.json, JSON.stringify(updatedData)); // Purge Next.js cache revalidatePath(payload.path); return NextResponse.json({ status: ok }); }核心逻辑可以拆成四步读取请求体中的页面 JSON读取现有database.json不存在则视为空对象{}以path为键、页面 JSON 为值合并写入database.json——这是典型的「以路径为 key 的键值存储」设计天然支持任意路径调用revalidatePath(payload.path)清除 Next.js 对该页面的静态缓存随后返回{ status: ok }。3. 读取侧lib/get-page.ts发布后的页面通过 recipes/next/lib/get-page.ts 读取import { Data } from puckeditor/core; import fs from fs; // Replace with call to your database export const getPage (path: string) { const allData: Recordstring, Data | null fs.existsSync(database.json) ? JSON.parse(fs.readFileSync(database.json, utf-8)) : null; return allData ? allData[path] : null; };它同样直接读取database.json按路径返回对应页面数据查询不到时返回null渲染端据此返回 404。revalidatePath清缓存后渲染端再被访问时会重新执行getPage读到最新数据并以静态页形式缓存——这正是「增量静态生成ISR」的典型用法。关键文件一览原文档以表格形式梳理了实现这条流程的各个文件整理如下路径均位于 recipes/next文件作用puck.config.tsx定义 Puck 可用的组件、字段与默认 props添加自有组件的入口app/puck/[...puckPath]/page.tsx为编辑器加载页面数据app/puck/[...puckPath]/client.tsx渲染编辑器并处理发布app/[...puckPath]/page.tsx加载并渲染已发布的页面app/puck/api/route.ts保存已发布的页面proxy.ts将以/edit结尾的 URL 改写路由到/puck/[...puckPath]/page.tsxlib/get-page.ts从database.json读取页面数据可替换为自己的数据获取逻辑database.json充当本地数据库可替换为真实数据库方案这套文件结构就是 recipe 的全部骨架编辑、保存、读取、渲染四件事各司其职且每处「数据读写」都被刻意收敛到单一函数getPage与单一端点/puck/api方便替换为真实后端。自定义组件配置从示例到生产级 Configrecipe 自带的 recipes/next/puck.config.tsx 只包含一个演示组件import type { Config } from puckeditor/core; type Props { HeadingBlock: { title: string }; }; export const config: ConfigProps { components: { HeadingBlock: { fields: { title: { type: text }, }, defaultProps: { title: Heading, }, render: ({ title }) ( div style{{ padding: 64 }} h1{title}/h1 /div ), }, }, }; export default config;这个示例展示了 Config 的三个基础用法fields声明组件可编辑字段type: text表示单行文本输入defaultProps为新拖入的组件提供默认属性这里默认标题为 Headingrender纯函数式渲染组件接收字段值返回 React 元素。如果你的需求更复杂可以参考apps/demo中更完整的配置 apps/demo/config/index.tsx它展示了真实应用的扩展方式root自定义根组件对应 apps/demo/config/root.tsx可用于统一页面布局categories将组件按「layout / typography / interactive / other」分类让左侧组件面板更有条理components注册十余个真实业务组件Heading、Text、RichText、Hero、Stats 等分布在 apps/demo/config/blocks 目录下每个组件的字段、样式与渲染逻辑都是独立的。以RichText富文本为例fields可以声明为{ type: richtext }编辑器会为其提供完整的富文本工具栏这类字段类型正是 Puck 相比普通「表单式页面构建器」的差异化能力之一。部署到生产环境前的必做清单原文档特别强调recipe 默认是「开箱即用、但面向本地开发」的上线前必须补齐以下四点1. 为/edit路由和 API 添加认证最重要默认情况下 Puck 完全公开。任何人访问你的域名/任意路径/edit都能打开编辑器任何人 POST 到/puck/api都能写入页面数据。必须修改 recipes/next/app/puck/api/route.ts 和 recipes/next/app/puck/[...puckPath]/page.tsx 两处代码加入身份认证与权限校验确保只有受信任的用户可以编辑或发布页面。2. 接入真实数据库database.json是本地文件存储在以下场景不可靠多台服务器实例各实例文件不共享写入互相覆盖Serverless / 无服务器部署文件系统通常是临时的函数退出后数据即丢失。你需要把 recipes/next/lib/get-page.ts 中的读取逻辑和 recipes/next/app/puck/api/route.ts 中的写入逻辑替换为对真实数据库PostgreSQL、MongoDB、Vercel KV 等的调用。由于数据读写被收敛在这两个位置替换成本很低。3. 打造自有组件库将puck.config.tsx中的示例HeadingBlock替换为你业务所需的组件与字段体系。组件库是 Puck 的核心资产——编辑器能拖拽的「积木」完全由 Config 决定。4. 选择渲染策略recipes/next/app/[...puckPath]/page.tsx 默认导出了dynamic force-static强制 Next.js 生成静态页面。如果页面需要请求时数据如读取 headers、cookies 或用户会话请删除该导出改用动态渲染。小结nextrecipe 用最少量的代码展示了 Puck 最强大的集成形态借助 App Router 的 Catch-all 路由 proxy.ts的 URL 重写任何路由都可以一键进入可视化编辑借助「以路径为键」的 JSON 存储模型与revalidatePath缓存失效机制新页面从创作到上线无需任何代码改动。无论你是在搭建 CMS、营销落地页平台还是内部建站工具这套「编辑端动态 渲染端静态」的双 Catch-all 架构都是值得直接借鉴的范式——只需再补上认证、真实数据库与业务组件库它就能成为一套完整的内容生产系统。【免费下载链接】puckThe visual editor for React.项目地址: https://gitcode.com/GitHub_Trending/puc/puck创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表