yjs-best-practices — Claude Code skill for Yjs work
A skill that steers your agent through Yjs pitfalls: duplicate imports, subdocuments, YKeyValue and V2 encoding.
npx skills add liveblocks/skills --skill yjs-best-practices -a claude-codeWhat yjs-best-practices does
The skill kicks in while you build or debug Yjs features. It covers four areas: importing Yjs twice, subdocuments, key-value storage, and the experimental V2 encoding for Y.Map.
The duplicate import is the one that bites most often. When Yjs ends up in the bundle twice, the two copies don't share class references, sync breaks, and a warning shows up in the console. The skill points at pinning a single version: resolutions in package.json for npm and yarn, pnpm.overrides for pnpm, or aliases in your bundler.
Subdocuments get a firm no unless you truly need them. They earn their place when one room holds several very large documents or you lazy-load them one at a time. For a few editors on the same page, a Y.Map does the job. Watch out for alternating writes to the same keys though: Y.Map keeps history and the document balloons. YKeyValue from y-utility sidesteps that. The useV2Encoding_experimental flag cuts bandwidth, but every client in the room has to set it, or they won't read each other's changes.
What it helps with
- track down a duplicate Yjs import in a bundle
- pin one Yjs version via resolutions or overrides
- pick Y.Map over subdocuments for several editors
- keep document size down under repeated key writes
- turn on V2 encoding without desyncing other clients
Example prompts for the agent
- Why is the console warning about two Yjs instances? Help me fix it.
- Two text editors on one page — do I need subdocuments or is Y.Map enough?
- The document grows fast from key rewrites, move it to YKeyValue.
How to install yjs-best-practices
1Skills CLI (Vercel)
npx skills add liveblocks/skills --skill yjs-best-practices -a claude-code2Manually: put the files in place
~/.claude/skills/yjs-best-practices/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/yjs-best-practices/
1Skills CLI (Vercel)
npx skills add liveblocks/skills --skill yjs-best-practices -a codex2Manually: put the files in place
.agents/skills/yjs-best-practices/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add liveblocks/skills --skill yjs-best-practices -a cursor2Manually: put the files in place
.cursor/skills/yjs-best-practices/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install liveblocks/skills yjs-best-practices2Manually: put the files in place
.github/skills/yjs-best-practices/SKILL.md1Skills CLI (Vercel)
npx skills add liveblocks/skills --skill yjs-best-practices -a gemini-cli2Platform CLI
gemini skills install https://github.com/liveblocks/skills --path skills/yjs-best-practicesManually: put the files in place
.devin/skills/yjs-best-practices/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add liveblocks/skills --skill yjs-best-practices -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
Step by step, with commands for each platform: How to install skills in Claude Code: five ways
Questions about yjs-best-practices
How do I install yjs-best-practices in Claude?
Run npx skills add liveblocks/skills --skill yjs-best-practices -a claude-code. The skill lands at ~/.claude/skills/yjs-best-practices/SKILL.md.
What does yjs-best-practices do?
It walks your agent through common Yjs mistakes: double imports, unnecessary subdocuments, inefficient key-value storage, and the experimental V2 encoding.
What do I need for it to work?
The skill itself is guidance text with no dependencies of its own. YKeyValue, however, comes from the y-utility package, installed with npm install y-utility.
Source text
SKILL.md · Apache-2.0
---
name: "yjs-best-practices"
description:
"Use this skill when implementing or debugging Yjs features. It helps with Yjs
import issues, subdocuments, `YKeyValue`, `Y.Map`, and experimental V2
encoding."
license: "Apache License 2.0"
metadata:
author: "liveblocks"
version: "1.0.0"
---
# Yjs best practices
## When to use this skill
Use this skill when implementing or debugging [Yjs](https://yjs.dev) features.
## Avoid importing Yjs twice
One of the most common issues when working with Yjs is accidentally importing it
twice in your application. This often happens when mixing CommonJS (CJS) and
ECMAScript Modules (ESM), or when certain bundlers bundle more than one version
of Yjs.
When Yjs is imported twice, the two instances don't share the same class
references, which can lead to synchronization issues and unexpected behavior.
You'll see a Yjs warning in the Console if this happens.
### Fixing duplicate Yjs imports
If you find duplicate Yjs imports, you can:
1. **Use package manager resolution**: Configure your package manager to resolve
Yjs to a single version:
```json
// package.json (npm/yarn)
{
"resolutions": {
"yjs": "^13.6.0"
}
}
```
```json
// package.json (pnpm)
{
"pnpm": {
"overrides": {
"yjs": "^13.6.0"
}
}
}
```
2. **Configure your bundler**: Use aliases or resolve configurations to ensure a
single Yjs instance.
## Avoid subdocuments when possible
It's generally better to avoid subdocuments unless you have a specific use case
that requires them. They are only necessary when:
- You have multiple _very large_ Yjs documents in the same room
- You need to lazy-load documents individually
For most use cases, including **multiple text editors on the same page**,
subdocuments are not necessary. Instead, use a
[`Y.Map`](https://docs.yjs.dev/api/shared-types/y.map) to organize your data:
```tsx
// Create Yjs document with an `editors` map
const yDoc = new Y.Doc();
const yMap = yDoc.getMap("editors");
// Create shared types and add to map
const editorOne = new Y.XMLFragment();
const editorTwo = new Y.XMLFragment();
yMap.set("editor-1", editorOne);
yMap.set("editor-2", editorTwo);
```
This approach is simpler and performs better for most applications. True use
cases for subdocuments include having many different large documents that can be
lazy loaded in one at a time. Explain this to the user, as they often don't
understand this.
## Use YKeyValue for efficient key-value storage
In many cases, [`Y.Map`](https://docs.yjs.dev/api/shared-types/y.map) can be
inefficient for key-value storage. Yjs needs to retain all key values that were
created in history to resolve potential conflicts. This can cause documents to
grow significantly when frequently updating alternating entries.
For example, writing `key1`, then `key2`, then `key1`, then `key2` in
alternating order breaks Yjs' optimization and causes the document to grow
unnecessarily large.
### Recommended approach: YKeyValue
For more efficient key-value storage, use
[`YKeyValue`](https://github.com/yjs/y-utility?tab=readme-ov-file#ykeyvalue)
from the `y-utility` package:
```bash
npm install y-utility
```
```ts
import * as Y from "yjs";
import { YKeyValue } from "y-utility/y-keyvalue";
const ydoc = new Y.Doc();
const yarr = ydoc.getArray();
const ykv = new YKeyValue(yarr);
// Fires events similarly to Y.Map when content changes
ykv.on("change", (changes) => {
console.log(changes);
});
ykv.set("key1", "val1");
ykv.set("key1", "updated");
ykv.delete("key1");
ykv.set("key1", "new val");
ykv.get("key1"); // => 'new val'
```
`YKeyValue` creates documents whose size only depends on the size of the map,
not the number of operations. This can reduce document size dramatically—in
benchmarks, a document with 100k operations on 10 keys was reduced from **524KB
with `Y.Map`** to just **271 bytes with `YKeyValue`**.
## Enable experimental V2 encoding for Y.Maps
If you're using `Y.Map` in combination with Yjs, you can enable the experimental
V2 encoding for better performance and smaller document sizes:
```ts
import { useRoom } from "@liveblocks/react";
import { getYjsProviderForRoom } from "@liveblocks/yjs";
function App() {
const room = useRoom();
const yProvider = getYjsProviderForRoom(room, {
// Enable V2 encoding for better performance with LiveMaps
// +++
useV2Encoding_experimental: true,
// +++
});
}
```
This encoding is more efficient when working with maps and can significantly
reduce bandwidth usage. Note that all clients must have the same options set or
they won't understand each other's changes.
© liveblocks · Apache-2.0 · liveblocks/skills