Server-side render fails with "requestAnimationFrame is not defined" when the room has a video node

Open Beginner friendly
#1,294 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@jesusmpc is already working on this.

Since Nov 12, 2026.

  • #1295 by @jesusmpc — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
79/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
node.js, typescript
Domain
backend, devtools

Research direction

Start with code/packages/sdk/src/stores/store.ts and the tests in code/packages/sdk/src/stores/__tests__/store.setup.test.ts; trace the afterTransaction handler and how it schedules renders. Check how brush-tool.ts handles environments without requestAnimationFrame, then add coverage for both a post-load transaction and a state write during setupRenderer(). Done when both cases avoid the error and the fallback schedules one render.

Written by the indexing model from the issue text.

Description

bug

Describe the bug

On the server, weave.start() rejects with ReferenceError: requestAnimationFrame is not defined when the room contains a video node, so server-side exports (image, PDF) of that room fail. Found in the weavejs-backend export jobs.

Two pieces of code combine:

  • WeaveStore.setup() (code/packages/sdk/src/stores/store.ts) handles every afterTransaction after the first load by scheduling the state events and instance.render() with requestAnimationFrame. That global does not exist in Node.js.
  • WeaveVideoNode.onRender() (code/packages/sdk/src/nodes/video/video.ts) calls this.instance.updateNode(...) when isServer(), so it writes state during the initial render.

That write starts a second Yjs transaction inside the store's first-load afterTransaction handler. Yjs runs the afterTransaction of the second transaction once the handler has returned, when isRoomLoaded is already true, so the store calls requestAnimationFrame and throws.

Any state change on the server after the room has loaded goes through the same call. The video node is the one that makes it happen during the load.

Steps to reproduce
  1. On the server, call await setupSkiaBackend().
  2. Create a Weave instance with WeaveStoreStandalone, WeaveKonvaBaseRenderer and WeaveVideoNode, with room data that has a video node in mainLayer.
  3. await weave.start() rejects:
ReferenceError: requestAnimationFrame is not defined
    at node_modules/@inditextech/weave-sdk/dist/sdk.js:1512:35
    at node_modules/lib0/observable.js:82:90
    at Array.forEach (<anonymous>)
    at Doc.emit (node_modules/lib0/observable.js:82:77)
    at Array.<anonymous> (node_modules/yjs/dist/yjs.mjs:3297:25)
    at callAll (node_modules/lib0/function.js:20:12)
    at cleanupTransactions (node_modules/yjs/dist/yjs.mjs:3298:7)
    at cleanupTransactions (node_modules/yjs/dist/yjs.mjs:3376:9)
    at transact (node_modules/yjs/dist/yjs.mjs:3424:9)
    at readUpdateV2 (node_modules/yjs/dist/yjs.mjs:1652:3)

The second transaction is started from:

Weave.updateNode
WeaveVideoNode.onRender
createInstance (renderer-konva-base)
WeaveKonvaBaseRenderer.buildSubtree
WeaveKonvaBaseRenderer.render
Weave.setupRenderer
WeaveStore afterTransaction handler (first load)

The same room without the video node loads and renders fine.

Expected behavior

The store works without requestAnimationFrame. Where it is not available, it defers with setTimeout(cb, 0) and still coalesces the updates into one render. brush-tool.ts already checks for requestAnimationFrame before using it.

Tests to add in code/packages/sdk/src/stores/__tests__/store.setup.test.ts:

  • Without requestAnimationFrame, a transaction after the first load does not throw, and render() runs once the timer fires.
  • A state write during setupRenderer() (the video case) does not throw.
Environment
  • Seen with @inditextech/weave-sdk 5.9.0-SNAPSHOT; the same code is in 5.8.0 and current main. The requestAnimationFrame batching was added in 5.0.0 (#1093).
  • Node.js 24, worker threads, Skia backend, WeaveStoreStandalone.
Additional context
  • The trace points to dist/sdk.js (browser build) because @inditextech/weave-store-standalone/server imports WeaveStore from @inditextech/weave-sdk instead of @inditextech/weave-sdk/server. Both builds have the same store code, so it is not the cause, but a server app ends up loading both builds.
  • Open question: whether WeaveVideoNode should write state from onRender() on the server at all.
Dominant language
TypeScript
Stars
276
Forks
26
Avg merge
1d 10h
Merged PRs (30d)
47

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from InditexTech/weavejs

All issues in InditexTech/weavejs

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.