feat(ENG-11821): add SSR support#146
Conversation
Make @stackone/hub safe to consume from server-rendered apps and restructure the dev sandboxes to match real-consumer usage. Bundle / runtime - Inject 'use client' banner into the ESM and CJS outputs (rollup output.banner) and configure terser with compress.directives: false so the directive survives minification. - Guard window.location.origin access in useIntegrationPicker so the hook is render-safe under SSR. - Guard customElements.define in WebComponentWrapper with typeof window/customElements checks plus a customElements.get registration check. - Add a sideEffects field marking only dist/webcomponent.js, so bundlers can tree-shake the React entry. Dev sandboxes - New dev/nextjs/ — Next.js 15 + React 19 App Router app that consumes the hub via "@stackone/hub": "file:../..", to validate SSR end-to-end (port 3002). - Move the existing Vite sandbox into dev/vite/ as its own package, also linking the hub via file:../.. (port 3001). resolve.dedupe is set for react/react-dom/react-hook-form so the symlinked hub doesn't end up with a second React instance. - Root scripts: npm run dev / dev:setup / dev:nextjs / dev:nextjs:setup / relay all delegate into the appropriate sandbox. Relay/Vite devDeps moved with the sandbox. Docs - README: Next.js (App Router) usage, suppressHydrationWarning note for theme application, and a troubleshooting section on the duplicate-React error covering Vite, webpack/Next, and pnpm fixes. - New CLAUDE.md capturing SSR invariants, sandbox conventions, and the single-React-instance constraint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
2 issues found across 34 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="dev/nextjs/app/page.tsx">
<violation number="1" location="dev/nextjs/app/page.tsx:5">
P1: Custom agent: **Flag Security Vulnerabilities**
Sensitive API key is sent to an unvalidated env-configured endpoint, allowing potential credential exfiltration or insecure HTTP transport.</violation>
</file>
<file name="CLAUDE.md">
<violation number="1" location="CLAUDE.md:27">
P3: The project layout entry incorrectly points the Vite sandbox to `dev/`; it should reference `dev/vite/` to match the actual structure and later instructions in the same file.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
Pull request overview
Adds SSR-safe importing for @stackone/hub (Next.js/Remix/etc.) and introduces dedicated Vite + Next.js sandboxes that consume the package via file:../.., alongside updated documentation for SSR and React deduping.
Changes:
- Injects a
'use client'banner into ESM/CJS bundles and configures terser to preserve directives. - Guards render-time DOM access (
window.location.origin,customElements.define) to prevent SSR crashes. - Restructures dev sandboxes into
dev/vite/and addsdev/nextjs/for SSR validation; updates scripts/docs accordingly.
Reviewed changes
Copilot reviewed 22 out of 34 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| vite.config.ts | Removes the old root Vite config (sandbox moved under dev/vite/). |
| src/modules/integration-picker/hooks/useIntegrationPicker.ts | Guards window.location.origin access for SSR safety. |
| src/WebComponentWrapper.tsx | Adds SSR-safe customElements registration guard + double-registration check. |
| src/StackOneHub.tsx | Adds 'use client' directive to the component module. |
| rollup.config.mjs | Adds 'use client' banners to ESM/CJS and preserves directives through terser. |
| package.json | Adds sideEffects for the webcomponent bundle; delegates dev scripts into sandboxes. |
| package-lock.json | Updates lockfile to reflect dependency/script re-org. |
| dev/vite/vite.config.ts | New Vite sandbox config with resolve.dedupe to avoid duplicate React instances. |
| dev/vite/* | New Vite sandbox package + relay config/schema and generated artifacts. |
| dev/nextjs/* | New Next.js App Router sandbox package to validate SSR behavior. |
| biome.json | Ignores __generated__ and .next output directories. |
| README.md | Documents SSR contract, sandboxes, Next.js usage, and duplicate-React troubleshooting. |
| CLAUDE.md | Adds repo conventions, commands, and SSR invariants documentation. |
Files not reviewed (1)
- dev/nextjs/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- .gitignore: stop ignoring .env.example so the dev sandboxes' example files actually get committed (the README referenced one that wasn't in the repo). - Add the missing dev/nextjs/.env.example and dev/vite/.env.example. - CLAUDE.md: project layout pointed Vite sandbox at dev/ instead of dev/vite/. - README: web component snippet was still using the old StackOneHub.web.js / <my-component> names — switched to webcomponent.js / <stackone-hub> to match what rollup actually emits. - dev/nextjs/app/HubWrapper.tsx: theme toggle had no accessible label. - dev/nextjs/app/page.tsx: refuse to send STACKONE_API_KEY to a NEXT_PUBLIC_API_URL that isn't HTTPS (or localhost) so a misconfigured env can't leak the key over plaintext. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 7 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="dev/nextjs/.env.example">
<violation number="1" location="dev/nextjs/.env.example:6">
P2: Use the Next.js sandbox port (3002) for `NEXT_PUBLIC_APP_URL` to avoid pointing local config at the wrong origin.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Summary
@stackone/hubsafe to import from server-rendered apps (Next.js App Router, Remix, etc.) by adding a'use client'banner to the bundle and guarding render-time DOM access.dev/nextjs/and restructure the existing Vite sandbox intodev/vite/so both consume the hub viafile:../..— same shape as a real consumer.CLAUDE.mdfor repo conventions.What changed
Bundle / runtime
'use client'is injected as a rollupoutput.bannerand survives terser viacompress.directives: false.useIntegrationPickernow guardswindow.location.originaccess (was thrown during render under SSR).WebComponentWrapperguardscustomElements.definewithtypeof window/customElementschecks plus acustomElements.getregistration check.package.jsonnow declaressideEffects: ["./dist/webcomponent.js"]so consumers tree-shake the React entry properly.Dev sandboxes
dev/nextjs/— Next 15 + React 19 App Router. Validates SSR end-to-end. Runs on:3002.dev/vite/— the existing Vite playground, now its own package. Runs on:3001.resolve.dedupeforreact/react-dom/react-hook-formis required because the symlinked hub atnode_modules/@stackone/hubresolves into a directory with its ownnode_modules(would otherwise hit "Invalid hook call").npm run dev,dev:setup,dev:nextjs,dev:nextjs:setup,relayall delegate into the right sandbox.Docs
suppressHydrationWarningon<html>(the malachite theme writes CSS variables todocumentElementpost-hydration), and a troubleshooting section for the duplicate-React issue covering Vite / webpack/Next / pnpm.CLAUDE.mddocumenting commands, project layout, SSR invariants, and the single-React-instance constraint.Test plan
npm run lint— clean (55 files, no fixes)npm run build— emits ESM, CJS, IIFE,.d.ts; ESM/CJS bundles begin with"use client";npm run dev:nextjs:setup && npm run dev:nextjs— Next sandbox boots on :3002,/returns HTTP 200, page is statically pre-renderednpm run dev:setup && npm run dev— Vite sandbox boots on :3001; served bundle resolves all React imports to a single Vite pre-bundle (verified via served module graph)🤖 Generated with Claude Code
Summary by cubic
Add SSR support to
@stackone/hubso it can be imported in Next.js App Router and other SSR frameworks without render-time errors. Includes a Next.js sandbox and docs to guide adoption, addressing ENG-11821.New Features
'use client'via Rollup banners; Terser preserves the directive.windowaccess inuseIntegrationPicker;WebComponentWrappercheckswindow/customElementsand avoids double registration;package.jsonsetssideEffects: ["./dist/webcomponent.js"]for proper tree-shaking.dev/nextjs; Vite sandbox moved todev/vitewithresolve.dedupeforreact,react-dom,react-hook-form. Root scripts delegate to each sandbox.dist/webcomponent.jsand<stackone-hub>; committed.env.examplefiles and adjusted.gitignore; Next.js sandbox refuses to sendSTACKONE_API_KEYto non-HTTPS/non-localhost API URLs; theme toggle now has an accessible label;CLAUDE.mdpath corrections.Migration
suppressHydrationWarningto the<html>tag in your root layout to avoid theme-related hydration warnings.react/react-dom(e.g., Viteresolve.dedupe, Next/Webpack aliases). Forpnpm, usepublic-hoist-pattern[]=react*orshamefully-hoist=true.Written for commit 3e38418. Summary will update on new commits. Review in cubic