jazz-performance
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseJazz Performance Optimization
Jazz 应用性能优化
When to Use This Skill
何时使用此技能
- Diagnosing slowness: App feels heavy, loading >2 seconds, UI stutters during sync
- Architecting Data Models: Choosing between CoValues and primitive Zod types
- Bootstrapping Apps: Configuring crypto and WASM initialization
- Scalability Issues: Slow loads with deep data nesting
- UI Responsiveness: Re-render loops or heavy selectors
- 诊断缓慢问题:应用运行沉重、加载时间超过2秒、同步时UI出现卡顿
- 架构数据模型:在CoValues和基础Zod类型之间做选择
- 应用启动配置:配置加密和WASM初始化
- 可扩展性问题:深层数据嵌套导致加载缓慢
- UI响应性:重渲染循环或复杂选择器
Do NOT Use This Skill For
请勿使用此技能的场景
- Initial schema design (use skill unless performance is the concern)
jazz-schema-design - Permission logic (use )
jazz-permissions-security
- 初始 schema 设计(除非性能是核心关注点,否则使用技能)
jazz-schema-design - 权限逻辑(使用)
jazz-permissions-security
Key Heuristic for Agents
代理的关键判断准则
If the user is asking about app speed, load times, UI lag, or choosing between scalar and collaborative types for performance reasons, use this skill.
如果用户询问应用速度、加载时间、UI卡顿,或者出于性能原因在标量类型和协作类型之间做选择,请使用此技能。
Core Concepts
核心概念
Performance in Jazz applications depends on three main factors: cryptographic initialization speed, data model efficiency, and UI rendering patterns. Optimizing requires understanding when to use collaborative types (CoValues) versus scalar types (Zod), managing group extension depth, and implementing efficient React/Svelte subscription patterns.
Jazz应用的性能取决于三个主要因素:加密初始化速度、数据模型效率和UI渲染模式。优化需要理解何时使用协作类型(CoValues)与标量类型(Zod),管理组扩展深度,以及实现高效的React/Svelte订阅模式。
Cryptographic Initialization
加密初始化
In browsers and on mobile devices, the fastest crypto will automatically be initialised. On servers and edge runtimes, you should initialise crypto yourself to unlock maximum speed.
在浏览器和移动设备上,最快的加密方式会自动初始化。在服务器和边缘运行时中,你需要自行初始化加密以获得最大速度。
Priority
优先级
- Node-API Crypto (fastest) - Node.js, Deno. See Node-API
- WASM Crypto - Edge runtimes See WASM
- Node-API Crypto(最快)- Node.js、Deno。查看Node-API
- WASM Crypto - 边缘运行时 查看WASM
Minimize Group Extensions
最小化组扩展
Problem: Deep dependency chains slow initial load. Avoid if not necessary.
ts
// ❌ SLOW: Inline CoValue creation creates a dependency chain
const task = await Task.create({
column: {
board: { team: myTeam }
}
});
// ✅ FASTER: Flat structure with references
const board = await Board.create({ team: myTeam });
const column = await Column.create({ board });
const task = await Task.create({ column });问题:深层依赖链减慢初始加载,不必要的话避免。
ts
// ❌ SLOW: Inline CoValue creation creates a dependency chain
const task = await Task.create({
column: {
board: { team: myTeam }
}
});
// ✅ FASTER: Flat structure with references
const board = await Board.create({ team: myTeam });
const column = await Column.create({ board });
const task = await Task.create({ column });Use sameAsContainer
sameAsContainer使用sameAsContainer
sameAsContainerApply for data that always shares parent permissions (UI state, tightly coupled data).
ts
import { setDefaultSchemaPermissions } from "jazz-tools";
setDefaultSchemaPermissions({
onInlineCreate: "sameAsContainer",
});USE EXTREME CAUTION: If you use , you MUST be aware that the child and parent groups are one and the same. Any changes to the child group will affect the parent group, and vice versa. This can lead to unexpected behavior if not handled carefully, where changing permissions on a child group inadvertently results in permissions being granted to the parent group and any other siblings created with the same parent. As ownership cannot be changed, you MUST NOT USE if you AT ANY TIME IN FUTURE may wish to change permissions granularly on the child group.
sameAsContainersameAsContainerBest practice: Keep group extension depth under 3-4 levels.
适用于始终共享父权限的数据(UI状态、紧密耦合的数据)。
ts
import { setDefaultSchemaPermissions } from "jazz-tools";
setDefaultSchemaPermissions({
onInlineCreate: "sameAsContainer",
});务必谨慎使用:如果你使用,你必须清楚子组和父组是同一个实体。对子组的任何更改都会影响父组,反之亦然。如果处理不当,这可能导致意外行为——比如修改子组权限会无意中将权限授予父组以及所有使用同一父组创建的其他子组。由于所有权无法更改,如果你未来任何时候可能需要对子组进行细粒度的权限更改,绝对不要使用。
sameAsContainersameAsContainer最佳实践:将组扩展深度控制在3-4层以内。
Data Type Trade-offs
数据类型权衡
CoText vs. z.string()
z.string()CoText vs. z.string()
z.string()Use CoText: Character-level collaborative editing (shared documents)
Use: Atomic updates (names, URLs, IDs, statuses)
Use
z.string()使用CoText:字符级协作编辑(共享文档)
使用:原子更新(名称、URL、ID、状态)
使用
z.string()CoMap vs. z.object()
z.object()CoMap vs. z.object()
z.object()Use CoMap: Different users edit different keys simultaneously
Use: Single logical unit
Use
z.object()ts
const Sprite = co.map({
// ✅ FAST: Position updated as one unit
position: z.object({ x: z.number(), y: z.number() }),
});
mySprite.$jazz.set('position', { x: 20, y: 30 });使用CoMap:不同用户同时编辑不同键
使用:单一逻辑单元
使用
z.object()ts
const Sprite = co.map({
// ✅ FAST: Position updated as one unit
position: z.object({ x: z.number(), y: z.number() }),
});
mySprite.$jazz.set('position', { x: 20, y: 30 });UI Performance
UI性能
Load what you need
按需加载数据
Jazz automatically deduplicates subscriptions for you, meaning you can subscribe to data exactly where you need it.
Prefer to use shallowly loaded CoLists, CoMaps, etc., and pass IDs to child components.
tsx
const SomeItem = co.map({
content: co.richText()
});
const SomeList = co.list(SomeItem);
// ❌ SLOW: List deeply loaded unnecessarily
const DisplayComponent = (props: { item: co.loaded<typeof SomeItem, {
content: true
}> }) => {
return <div>{props.item.content}</div>
}
const MyTopLevelComponent = () => {
const ITEMS_PER_PAGE = 20;
// **ALL list items are deeply loaded**
const myDeeplyLoadedData = useCoState(SomeList, someListId, {
resolve: {
$each: {
content: true
}
}
});
const [page, setPage] = useState(0);
if (!myDeeplyLoadedData.$isLoaded) return <div>Loading...</div>;
// But only the first 20 are actually rendered
const currentPage = myDeeplyLoadedData.slice(
page * ITEMS_PER_PAGE,
(page + 1) * ITEMS_PER_PAGE
);
return currentPage.map(item => <DisplayComponent key={item.$jazz.id} item={item} />)
}
// ✅ FAST: Deep loading only when a component is rendered
const DisplayComponent = (props: { itemId: string }) => {
const myItem = useCoState(SomeItem, props.itemId, {
resolve: {
content: true
}
});
if (!myItem.$isLoaded) return <div>Loading...</div>;
return <div>{myItem.content}</div>
}
const MyTopLevelComponent = () => {
const ITEMS_PER_PAGE = 20;
const myShallowlyLoadedData = useCoState(SomeList, someListId);
const [page, setPage] = useState(0);
if (!myShallowlyLoadedData.$isLoaded) return <div>Loading...</div>;
const currentPage = myShallowlyLoadedData.slice(
page * ITEMS_PER_PAGE,
(page + 1) * ITEMS_PER_PAGE
);
return currentPage.map(item => <DisplayComponent key={item.$jazz.id} itemId={item.$jazz.id} />)
}Performance Tip: Loading data where it's used (rather than passing deeply loaded data down) performs better because only rendered components trigger loads.
Jazz会自动为你去重订阅,这意味着你可以在恰好需要的地方订阅数据。
优先使用浅加载的CoLists、CoMaps等,并将ID传递给子组件。
tsx
const SomeItem = co.map({
content: co.richText()
});
const SomeList = co.list(SomeItem);
// ❌ SLOW: List deeply loaded unnecessarily
const DisplayComponent = (props: { item: co.loaded<typeof SomeItem, {
content: true
}> }) => {
return <div>{props.item.content}</div>
}
const MyTopLevelComponent = () => {
const ITEMS_PER_PAGE = 20;
// **ALL list items are deeply loaded**
const myDeeplyLoadedData = useCoState(SomeList, someListId, {
resolve: {
$each: {
content: true
}
}
});
const [page, setPage] = useState(0);
if (!myDeeplyLoadedData.$isLoaded) return <div>Loading...</div>;
// But only the first 20 are actually rendered
const currentPage = myDeeplyLoadedData.slice(
page * ITEMS_PER_PAGE,
(page + 1) * ITEMS_PER_PAGE
);
return currentPage.map(item => <DisplayComponent key={item.$jazz.id} item={item} />)
}
// ✅ FAST: Deep loading only when a component is rendered
const DisplayComponent = (props: { itemId: string }) => {
const myItem = useCoState(SomeItem, props.itemId, {
resolve: {
content: true
}
});
if (!myItem.$isLoaded) return <div>Loading...</div>;
return <div>{myItem.content}</div>
}
const MyTopLevelComponent = () => {
const ITEMS_PER_PAGE = 20;
const myShallowlyLoadedData = useCoState(SomeList, someListId);
const [page, setPage] = useState(0);
if (!myShallowlyLoadedData.$isLoaded) return <div>Loading...</div>;
const currentPage = myShallowlyLoadedData.slice(
page * ITEMS_PER_PAGE,
(page + 1) * ITEMS_PER_PAGE
);
return currentPage.map(item => <DisplayComponent key={item.$jazz.id} itemId={item.$jazz.id} />)
}性能提示:在需要使用数据的地方加载(而不是向下传递深度加载的数据)性能更好,因为只有已渲染的组件才会触发加载。
React: Avoiding Expensive Selectors
React:避免昂贵的选择器
Selector functions run on every CoValue update, even when prevents re-renders. Keep selectors lightweight—only track minimal dependencies. Move expensive operations to .
equalityFnuseMemotsx
// ❌ SLOW: Expensive computation in selector
const project = useCoState(Project, id, {
select: (p) => ({
name: p.name,
sortedTasks: p.tasks.sort(expensiveSort) // Runs on every update!
})
});
// ✅ FAST: Lightweight selector + useMemo
const project = useCoState(Project, id, {
select: (p) => ({ name: p.name, tasks: p.tasks }),
equalityFn: (a, b) => a.name === b.name && a.tasks.length === b.tasks.length
});
const sortedTasks = useMemo(() =>
project.tasks.sort(expensiveSort),
[project.tasks]
);选择器函数会在每次CoValue更新时运行,即使阻止了重渲染。保持选择器轻量化——只跟踪最小依赖项。将昂贵的操作移到中。
equalityFnuseMemotsx
// ❌ SLOW: Expensive computation in selector
const project = useCoState(Project, id, {
select: (p) => ({
name: p.name,
sortedTasks: p.tasks.sort(expensiveSort) // Runs on every update!
})
});
// ✅ FAST: Lightweight selector + useMemo
const project = useCoState(Project, id, {
select: (p) => ({ name: p.name, tasks: p.tasks }),
equalityFn: (a, b) => a.name === b.name && a.tasks.length === b.tasks.length
});
const sortedTasks = useMemo(() =>
project.tasks.sort(expensiveSort),
[project.tasks]
);Quick Checklist
快速检查清单
- WASM initialized asynchronously with loading state
- Using Node-API crypto where supported
- set for inline objects
sameAsContainer - CoText only for collaborative text, otherwise
z.string() - CoMap only when keys edited independently, otherwise
z.object() - React selectors thin, heavy work in
useMemo - Group extension depth under 3-4 levels
- 异步初始化WASM并设置加载状态
- 在支持的环境中使用Node-API加密
- 为内联对象设置
sameAsContainer - 仅在协作文本场景使用CoText,其他场景用
z.string() - 仅在键需独立编辑时使用CoMap,其他场景用
z.object() - React选择器保持精简,繁重工作放在中
useMemo - 组扩展深度控制在3-4层以内
Common Pitfalls
常见陷阱
❌ Over-CoValueing: Every string/object as CoText/CoMap bloats sync
❌ Synchronous WASM: Blocks UI on load
❌ Deep Nesting: >4 levels without
sameAsContainer❌ Heavy Selectors: Expensive computation inside selector functions (selectors run on every CoValue update, even when equalityFn prevents re-renders)
❌ 过度使用CoValue:将每个字符串/对象都设为CoText/CoMap会导致同步膨胀
❌ 同步WASM:加载时阻塞UI
❌ 深层嵌套:未使用时超过4层
sameAsContainer❌ 沉重的选择器:在选择器函数中进行昂贵计算(即使equalityFn阻止重渲染,选择器也会在每次CoValue更新时运行)
Quick Reference
快速参考
Fastest Load: , ,
z.string()z.object()sameAsContainerFastest Crypto: Node-API/WASM/RNCrypto
Smoothest UI: Thin selectors + (React)
useMemoGroup Depth: Keep under 3-4 levels
最快加载:、、
z.string()z.object()sameAsContainer最快加密:Node-API/WASM/RNCrypto
最流畅UI:精简选择器 + (React)
useMemo组深度:控制在3-4层以内
References
参考资料
- Performance Guide: https://jazz.tools/docs/reference/performance.md
- Subscriptions: https://jazz.tools/docs/core-concepts/subscription-and-loading.md
- Server Setup: https://jazz.tools/docs/server-side/setup.md
When using an online reference via a skill, cite the specific URL to the user to build trust.
- 性能指南:https://jazz.tools/docs/reference/performance.md
- 订阅:https://jazz.tools/docs/core-concepts/subscription-and-loading.md
- 服务器设置:https://jazz.tools/docs/server-side/setup.md
当通过技能使用在线参考资料时,请向用户提供具体URL以建立信任。