为什么GrapesJS适合Remix
GrapesJS 需要 DOM,所以在 Remix 应用里你会在里面初始化它
useEffect (仅客户端)而 Remix 的加载器和动作处理数据
在服务器上。本指南安装编辑器,通过Remix动作保存,并且
导出HTML/CSS。
1. 挂载编辑器客户端
创造 app/routes/editor.tsx。在效果中导入GrapesJS,所以它
SSR期间从不运行。
import { useEffect, useRef } from 'react';
import 'grapesjs/dist/css/grapes.min.css';
export default function Editor() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
let editor: any;
(async () => {
const grapesjs = (await import('grapesjs')).default;
editor = grapesjs.init({
container: ref.current!,
height: '100vh',
fromElement: false,
storageManager: false,
components: '<h1>Hello from GrapesJS</h1>',
});
// Persist via a Remix action.
document.getElementById('save')!.onclick = async () => {
await fetch('/editor', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
html: editor.getHtml(),
css: editor.getCss(),
project: editor.getProjectData(),
}),
});
};
})();
return () => editor?.destroy();
}, []);
return (
<>
<button id="save">Save</button>
<div ref={ref} />
</>
);
}
2. 在混音操作中处理存档
// same app/routes/editor.tsx
import type { ActionFunctionArgs } from '@remix-run/node';
import { json } from '@remix-run/node';
import { savePage } from '~/models/page.server';
export async function action({ request }: ActionFunctionArgs) {
const data = await request.json();
await savePage('home', data); // your DB write
return json({ status: 'ok' });
}
操作运行在服务器端,所以你的数据库客户端不会进入浏览器 捆绑。
3. 返回保存的内容
export async function loader() {
return json(await getPage('home'));
}
// in the effect, after init:
// const saved = await fetch('/editor?_data=...'); editor.loadProjectData(saved.project);
混音版常见的陷阱
Remix的服务器/客户端分离出了问题。在模块顶层导入 GrapesJS 时,在 SSR 期间运行它,导致路由崩溃——而是动态导入到内部 useEffect 。保持 action 所有持久化在路由(服务器)中,切勿将数据库客户端导入组件,否则会泄漏到浏览器捆绑包中。如果你加载了保存的项目,先在 init 后读取并 loader 调用 editor.loadProjectData() ——不要在 SSR 期间尝试渲染编辑器状态。最后,从效果中返回 editor.destroy() ,这样客户端的过渡不会堆叠编辑器实例。
前提条件
你需要Node.js 18+ 和一个 Remix 2 应用。没有专门针对 Remix 的 GrapesJS 包
必须——编辑器仅支持浏览器,Remix的加载器/操作处理数据
服务器。熟悉路由, useEffect以及混音动作
够了。
向编辑器添加自定义方块
init 后(效果内)用块管理器注册可拖拽的块:
editor.BlockManager.add('hero', {
label: 'Hero section',
category: 'Sections',
content: '<section class="hero"><h1>Headline</h1><p>Copy</p></section>',
});
从GJS拉取现成的块库和预设。市场要买更丰富的套装。
存储深度潜航:动作 + 加载器
保持服务器的持久性。将项目发布到该路线 action
然后从中 loader读取,这样你的数据库客户端就不会泄露
进入浏览器捆绑包:
export async function action({ request }) {
const data = await request.json();
await savePage('home', data); // server-only DB write
return json({ status: 'ok' });
}
export async function loader() {
return json(await getPage('home')); // returns the saved project
}
init 后,呼叫 editor.loadProjectData(saved.project) 重新打开页面。
表演技巧
动态导入GrapesJS useEffect ,避免它进入
主捆绑包和服务器渲染路径,以及返回 editor.destroy()
所以客户端切换不会叠加实例。代码分段繁重插件
就是使用这些元素的功能。
安全考量
在写作前确认并授权该行为——绝不要接受 未经认证的帖子覆盖页面。如果 非管理员可以编辑。验证有效载荷大小,这样大型项目就不会耗尽 记忆。
排查常见问题
“窗口/文档未定义” 意味着GrapesJS在SSR期间运行——
导入并只在 useEffect内部启动。 一个未被打扮的
Canvas 意味着样式表导入缺失。 一片空白
编辑器 意味着容器引用在初始化时还没准备好。补水警告
通常意味着你在SSR期间尝试渲染编辑器状态。
什么时候用GrapesJS搭配Remix
当你的 Remix 应用嵌入真实的可视化页面或邮件构建器时,GrapesJS 就很适合你的 用户控制,拥有自己的存储和HTML输出。对于内嵌富文本,a 更轻的编辑器就足够了;用于全页构图,包含版面、样式和 干净导出,GrapesJS 是更强大的、获得麻省理工学院许可的自托管选择。
下一步
参见相关的 GrapesJS + React 和 GrapesJS + Next.js 指南,请浏览 GrapesJS 市场,或者从那里开始 GJS。市场主页。
常见问题
GrapesJS 能和 Remix 服务器渲染一起使用吗?
是的——只需 useEffect 在浏览器中初始化,确保它能在浏览器中运行。该
路由由服务器渲染;只有编辑器实例是仅客户端的。
我如何在Remix中保存GrapesJS的数据?
把项目数据发布到Remix操作中,并持久保存到你的数据库中。行动 在服务器上运行。
我该如何重新加载保存的内容?
从加载器获取已保存的项目并调用
editor.loadProjectData(saved) 之后 grapesjs.init。
