Skip to content

SSR broken with engine >= 2.20.0: module-scope getNullApplication() throws (mock canvas lacks getBoundingClientRect) #335

Description

@willeastcott

Description

@playcanvas/react crashes at import time in Node/SSR environments when used with playcanvas engine ≥ 2.20.0.

getNullApplication() in src/utils/validation.ts creates its SSR mock canvas as a bare object:

const mockCanvas = { id: 'pc-react-mock-canvas' };
return new Application(mockCanvas, { graphicsDevice: new NullGraphicsDevice(mockCanvas) });

Since engine v2.20.0 (playcanvas/engine#8950), the base GraphicsDevice constructor calls this.canvas.getBoundingClientRect(), so new NullGraphicsDevice(mockCanvas) now throws:

TypeError: this.canvas.getBoundingClientRect is not a function

This is compounded by the module-scope call in the same file:

const localApp = getNullApplication();
export const getStaticNullApplication = () => localApp;

Because localApp is created eagerly during module evaluation, any import of @playcanvas/react in Node throws — there is no way for consumers to catch or avoid it.

Impact

Broke the developer.playcanvas.com Docusaurus build (static site generation failed for all 639 doc pages) after upgrading to playcanvas 2.20.4. The site is temporarily pinned to engine ~2.19.7 (playcanvas/developer-site#1074).

The engine-side regression is tracked in playcanvas/engine#8999, but this package should be robust regardless.

Suggested fixes

  1. Give the mock canvas a getBoundingClientRect stub (zeroed rect: { left: 0, top: 0, right: 0, bottom: 0, width: 0, height: 0 }) plus any other DOM surface the device constructor path touches.
  2. Make localApp lazy — initialize on first getStaticNullApplication() call — so a failure inside getNullApplication() can't break module evaluation for every importer.

Verified against playcanvas 2.20.4 (throws) and 2.19.7 (works).

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions