jazz-performance

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Jazz 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
    jazz-schema-design
    skill unless performance is the concern)
  • 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

优先级

  1. Node-API Crypto (fastest) - Node.js, Deno. See Node-API
  2. WASM Crypto - Edge runtimes See WASM
  1. Node-API Crypto(最快)- Node.js、Deno。查看Node-API
  2. 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

Apply 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
sameAsContainer
, 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
sameAsContainer
if you AT ANY TIME IN FUTURE may wish to change permissions granularly on the child group.
Best practice: Keep group extension depth under 3-4 levels.
适用于始终共享父权限的数据(UI状态、紧密耦合的数据)。
ts
import { setDefaultSchemaPermissions } from "jazz-tools";

setDefaultSchemaPermissions({
  onInlineCreate: "sameAsContainer",
});
务必谨慎使用:如果你使用
sameAsContainer
,你必须清楚子组和父组是同一个实体。对子组的任何更改都会影响父组,反之亦然。如果处理不当,这可能导致意外行为——比如修改子组权限会无意中将权限授予父组以及所有使用同一父组创建的其他子组。由于所有权无法更改,如果你未来任何时候可能需要对子组进行细粒度的权限更改,绝对不要使用
sameAsContainer
最佳实践:将组扩展深度控制在3-4层以内。

Data Type Trade-offs

数据类型权衡

CoText vs.
z.string()

CoText vs.
z.string()

Use CoText: Character-level collaborative editing (shared documents)
Use
z.string()
:
Atomic updates (names, URLs, IDs, statuses)
使用CoText:字符级协作编辑(共享文档)
使用
z.string()
:原子更新(名称、URL、ID、状态)

CoMap vs.
z.object()

CoMap vs.
z.object()

Use CoMap: Different users edit different keys simultaneously
Use
z.object()
:
Single logical unit
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
equalityFn
prevents re-renders. Keep selectors lightweight—only track minimal dependencies. Move expensive operations to
useMemo
.
tsx
// ❌ 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更新时运行,即使
equalityFn
阻止了重渲染。保持选择器轻量化——只跟踪最小依赖项。将昂贵的操作移到
useMemo
中。
tsx
// ❌ 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
  • sameAsContainer
    set for inline objects
  • CoText only for collaborative text,
    z.string()
    otherwise
  • CoMap only when keys edited independently,
    z.object()
    otherwise
  • 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
深层嵌套:未使用
sameAsContainer
时超过4层
沉重的选择器:在选择器函数中进行昂贵计算(即使equalityFn阻止重渲染,选择器也会在每次CoValue更新时运行)

Quick Reference

快速参考

Fastest Load:
z.string()
,
z.object()
,
sameAsContainer
Fastest Crypto: Node-API/WASM/RNCrypto
Smoothest UI: Thin selectors +
useMemo
(React)
Group Depth: Keep under 3-4 levels
最快加载
z.string()
z.object()
sameAsContainer
最快加密:Node-API/WASM/RNCrypto
最流畅UI:精简选择器 +
useMemo
(React)
组深度:控制在3-4层以内

References

参考资料

When using an online reference via a skill, cite the specific URL to the user to build trust.
当通过技能使用在线参考资料时,请向用户提供具体URL以建立信任。