Skip to content

Comments

feat(ssr): make cloud components safe for SSR and hydration#431

Open
revaarathore11 wants to merge 2 commits intoelixir-cloud-aai:mainfrom
revaarathore11:feat/ssr-support
Open

feat(ssr): make cloud components safe for SSR and hydration#431
revaarathore11 wants to merge 2 commits intoelixir-cloud-aai:mainfrom
revaarathore11:feat/ssr-support

Conversation

@revaarathore11
Copy link

@revaarathore11 revaarathore11 commented Feb 19, 2026

Description

This PR adds Server-Side Rendering (SSR) safety to Cloud Components so they can be
imported and used in SSR environments (e.g. Next.js, Remix) without crashes or
hydration mismatches
.

Web Components remain client-rendered, but this change ensures:

  • No browser-only APIs run during server execution
  • No hydration issues caused by non-deterministic IDs
  • Safe and idempotent custom element registration

Fixes #418

What changed

-Added shared SSR utilities (isBrowser, isServer, ssrSafeDefine) for
environment detection and safe custom element registration

  • Guarded all customElements.define() calls across packages
  • Replaced Math.random() IDs with deterministic generators
  • Prevented browser API usage during server execution
  • Added comprehensive SSR documentation (docs/SSR.md) with Next.js/Remix guidance

Verification

  • All packages build successfully
  • Importing @elixir-cloud/design in Node.js does not crash
  • No remaining non-deterministic render paths
  • No breaking API changes (fully backward compatible)

Notes

This PR intentionally avoids framework-specific logic and keeps changes minimal
and reviewable while addressing the core SSR and hydration issues.

Summary by Sourcery

Add SSR-safe utilities and guards to make cloud web components safe to import in server-side environments while avoiding hydration issues.

New Features:

  • Introduce SSR utility module providing environment detection, safe custom element registration, and deterministic ID generation.
  • Export SSR helpers from the design utilities package for external consumers.

Enhancements:

  • Wrap all custom element registrations across client packages with environment and duplicate-registration guards for SSR safety.
  • Replace random ID generation in key design components with deterministic counters to prevent SSR hydration mismatches.
  • Guard browser-only APIs in design components to ensure they do not execute during server-side rendering.

Documentation:

  • Add an SSR compatibility guide documenting usage patterns, utilities, and best practices for using components with frameworks like Next.js and Remix.

@vercel
Copy link

vercel bot commented Feb 19, 2026

@revaarathore11 is attempting to deploy a commit to the elixir-cloud-aai Team on Vercel.

A member of the Team first needs to authorize it.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 19, 2026

Reviewer's Guide

Adds SSR-safe utilities and guards across the design and client packages so web components can be imported in server environments without crashing or causing hydration mismatches, including safe custom element registration, deterministic ID generation, and SSR documentation.

Class diagram for new SSR utilities and updated components

classDiagram
  class SsrUtils {
    - idCounter : number
    + isBrowser() boolean
    + isServer() boolean
    + ssrSafeDefine(tagName string, constructor CustomElementConstructor) void
    + generateDeterministicId(prefix string) string
    + resetIdCounter() void
  }

  class EccUtilsDesignCollapsible {
    - collapsibleId : string
    + open : boolean
    + disabled : boolean
  }

  class EccUtilsDesignMultiSelect {
    - selectId : string
    + value : string[]
    + placeholder : string
    + disabled : boolean
  }

  class EccUtilsDesignSelect {
    - selectId : string
    + value : string
    + disabled : boolean
  }

  class EccUtilsDesignCode {
    + setupThemeObserver() void
    + isDarkMode() boolean
  }

  SsrUtils <.. EccUtilsDesignCollapsible : uses generateDeterministicId
  SsrUtils <.. EccUtilsDesignMultiSelect : uses generateDeterministicId
  SsrUtils <.. EccUtilsDesignSelect : uses generateDeterministicId
  SsrUtils <.. EccUtilsDesignCode : uses isBrowser
Loading

File-Level Changes

Change Details Files
Guard all custom element registrations so imports are safe in SSR/Node and duplicate definitions are avoided.
  • Wrap each customElements.define() call in a window/customElements existence check
  • Add a pre-registration lookup via customElements.get(tagName) to avoid duplicate registrations during HMR or multiple imports
packages/ecc-client-ga4gh-service-registry/src/components/service/index.ts
packages/ecc-client-ga4gh-wes/src/components/run-create/index.ts
packages/ecc-client-elixir-cloud-registry/src/components/service-create/index.ts
packages/ecc-client-elixir-drs-filer/src/components/object-create/index.ts
packages/ecc-client-elixir-ro-crate/src/components/about/index.ts
packages/ecc-client-elixir-trs-filer/src/components/tool-create/index.ts
packages/ecc-client-ga4gh-drs/src/components/objects-list/index.ts
packages/ecc-client-ga4gh-service-registry/src/components/services/index.ts
packages/ecc-client-ga4gh-tes/src/components/create-run/index.ts
packages/ecc-client-ga4gh-tes/src/components/runs/index.ts
packages/ecc-client-ga4gh-trs/src/components/tools/index.ts
packages/ecc-client-ga4gh-drs/src/components/object/index.ts
packages/ecc-client-ga4gh-trs/src/components/tool/index.ts
packages/ecc-client-ga4gh-wes/src/components/run/index.ts
packages/ecc-client-ga4gh-wes/src/components/runs/index.ts
Introduce shared SSR utilities and deterministic ID generation and use them in design components to avoid hydration mismatches and server-only crashes.
  • Add an ssr.ts module exporting isBrowser, isServer, ssrSafeDefine, generateDeterministicId, and resetIdCounter
  • Re-export SSR utilities from the design package entrypoint for external use
  • Replace Math.random()-based IDs in Collapsible, Select, and MultiSelect components with generateDeterministicId()
  • Guard browser-only logic (MutationObserver, document access) in the Code component with isBrowser() checks
packages/ecc-utils-design/src/ssr.ts
packages/ecc-utils-design/src/index.ts
packages/ecc-utils-design/src/components/collapsible/collapsible.ts
packages/ecc-utils-design/src/components/multi-select/multi-select.ts
packages/ecc-utils-design/src/components/select/select.ts
packages/ecc-utils-design/src/components/code/code.ts
Add user-facing SSR documentation explaining how to integrate the components with common SSR frameworks and how the SSR-safety mechanisms work.
  • Create an SSR.md guide under docs describing environment guards, deterministic IDs, browser-only lifecycle usage, and troubleshooting
  • Document recommended patterns for using the library with Next.js (App Router) and Remix, including dynamic imports and client-only components
  • Describe the exported SSR helper API and current status/limitations around Declarative Shadow DOM and server-side rendering
docs/SSR.md

Assessment against linked issues

Issue Objective Addressed Explanation
#418 Ensure web components are safe in SSR environments by guarding browser-only APIs (window, document, customElements, MutationObserver, etc.), using deterministic IDs to avoid hydration mismatches, and making custom element registration idempotent.
#418 Introduce SSR-focused utilities and documentation that explain how to use the components with SSR frameworks (e.g., Next.js, Remix), including environment detection helpers and best practices.
#418 Implement actual server-side rendering of the web components’ DOM (e.g., via Declarative Shadow DOM and/or @lit-labs/ssr) to provide real server-rendered markup for better initial load and SEO, potentially including SSR-friendly React bindings. The PR explicitly limits itself to SSR safety (no crashes, no hydration mismatches) and notes that DSD-based server rendering is not yet implemented. Components remain client-rendered, and there is no integration of @lit-labs/ssr, no generation of Declarative Shadow DOM templates, and no SSR-specific React bindings.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@changeset-bot
Copy link

changeset-bot bot commented Feb 19, 2026

⚠️ No Changeset found

Latest commit: ef781a2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • You’ve added an ssrSafeDefine helper but the various index.ts files for components still inline their own typeof window !== 'undefined' guards; consider refactoring those to use ssrSafeDefine so the SSR registration logic stays consistent and centralized.
  • The deterministic ID generator uses a single global counter across all component types; if you ever need prefix-local sequencing or more predictable IDs per component kind, it might be cleaner to keep per-prefix counters (e.g., a map of counters keyed by prefix) rather than sharing one global counter.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- You’ve added an `ssrSafeDefine` helper but the various `index.ts` files for components still inline their own `typeof window !== 'undefined'` guards; consider refactoring those to use `ssrSafeDefine` so the SSR registration logic stays consistent and centralized.
- The deterministic ID generator uses a single global counter across all component types; if you ever need prefix-local sequencing or more predictable IDs per component kind, it might be cleaner to keep per-prefix counters (e.g., a map of counters keyed by prefix) rather than sharing one global counter.

## Individual Comments

### Comment 1
<location> `packages/ecc-utils-design/src/components/code/code.ts:649-655` </location>
<code_context>
   }

   setupThemeObserver() {
+    // SSR guard: MutationObserver and document are not available on the server
+    if (!isBrowser()) return;
+
     // Set up mutation observer to watch for theme changes
     const observer = new MutationObserver(() => {
       this.updateEditorTheme();
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Guard against environments where `MutationObserver` exists on `window` but is undefined or not supported.

In some constrained or older browser-like environments, `window` exists but `MutationObserver` does not, which would cause a runtime error when you instantiate it. Consider guarding its presence before use, e.g.:

```ts
if (!isBrowser() || typeof MutationObserver === "undefined") return;
```

before creating the observer.

```suggestion
  setupThemeObserver() {
    // Guard: MutationObserver and document are not available on the server or in some constrained environments
    if (!isBrowser() || typeof MutationObserver === "undefined") return;

    // Set up mutation observer to watch for theme changes
    const observer = new MutationObserver(() => {
      this.updateEditorTheme();
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@revaarathore11
Copy link
Author

@uniqueg @anuragxxd please review

@Karanjot786 Karanjot786 requested a review from uniqueg February 21, 2026 14:00
@uniqueg uniqueg requested a review from anuragxxd February 21, 2026 14:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Server-Side Rendering (SSR) Support for Web Components

1 participant