-
Notifications
You must be signed in to change notification settings - Fork 5
feat(ENG-11821): add SSR support #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
adefreitas
merged 2 commits into
StackOneHQ:main
from
adefreitas:ENG-11821/add-ssr-support
Apr 29, 2026
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json | |
|
|
||
| # environment variables | ||
| .env* | ||
| !.env.example | ||
|
|
||
| # MacOS | ||
| .DS_Store | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # StackOne Hub — Claude instructions | ||
|
|
||
| `@stackone/hub` is a React component library that ships an embeddable integration picker. It's bundled with Rollup and consumed in three forms: ESM, CJS, and a self-contained web component IIFE. | ||
|
|
||
| ## Commands | ||
|
|
||
| | Task | Command | | ||
| |------|---------| | ||
| | Build all bundles | `npm run build` | | ||
| | Vite dev sandbox (port 3001) | `npm run dev` | | ||
| | Next.js SSR sandbox (port 3002) | `npm run dev:nextjs` | | ||
| | First-time Next sandbox setup | `npm run dev:nextjs:setup` | | ||
| | Lint | `npm run lint` (Biome) | | ||
| | Auto-fix lint/format | `npm run lint:fix` and `npm run code:format:fix` | | ||
| | Regenerate Relay artifacts | `npm run relay` | | ||
|
|
||
| Always run `npm run lint` before committing. Biome is the only linter — don't add ESLint/Prettier configs. | ||
|
|
||
| ## Project layout | ||
|
|
||
| - `src/index.ts` — public entry, exports `StackOneHub` | ||
| - `src/StackOneHub.tsx` — top-level component (carries the `'use client'` directive) | ||
| - `src/Hub.tsx` — inner component, switches on `mode` | ||
| - `src/modules/integration-picker/` — main feature | ||
| - `src/shared/` — error boundary, http client, feature flags, queries | ||
| - `src/WebComponentWrapper.tsx` — separate entry built into `dist/webcomponent.js` | ||
| - `dev/vite/` — Vite-based dev sandbox (own `package.json`, hub linked via `file:../..`) | ||
| - `dev/nextjs/` — Next.js 15 + React 19 App Router SSR sandbox (own `package.json`, hub linked via `file:../..`) | ||
| - `rollup.config.mjs` — three bundle outputs (ESM main, CJS main, IIFE webcomponent) plus a `.d.ts` rollup | ||
| - `dist/` — build output, gitignored, the only thing published | ||
|
|
||
| ## SSR / Next.js constraints | ||
|
|
||
| The package is consumed by SSR frameworks (Next.js App Router). When changing `src/`, keep these invariants intact: | ||
|
|
||
| 1. **`'use client'` directive on the bundle.** It's injected by `output.banner` in `rollup.config.mjs` and survives minification because terser is configured with `compress: { directives: false }`. If you change the rollup config, verify `head -c 30 dist/index.esm.js` still starts with `"use client";`. | ||
| 2. **No `window`/`document`/`localStorage` access during render.** Anything that touches the DOM must live inside `useEffect`, an event handler, or be guarded with `typeof window !== 'undefined'`. Render-time access (including `useMemo` and module-scope code) breaks SSR. | ||
| 3. **`customElements.define` is module-scoped in `WebComponentWrapper.tsx`** and is guarded with `typeof window !== 'undefined' && typeof customElements !== 'undefined'` plus a `customElements.get` check. Keep the guards if you edit that file. | ||
| 4. **Theme application mutates `<html>`.** `applyTheme`/`applyLightTheme`/`applyDarkTheme` from `@stackone/malachite` set CSS custom properties on `document.documentElement`. This requires consumers to add `suppressHydrationWarning` to their `<html>` tag (documented in README). Don't move these calls out of `useEffect`. | ||
| 5. **`package.json` `sideEffects` field** marks only `./dist/webcomponent.js` as side-effecting so bundlers can tree-shake the React entry. Keep it that way. | ||
| 6. **Single React instance.** `react`/`react-dom`/`react-hook-form` are peer deps and the bundle imports them at runtime — two copies in the consumer's tree breaks hooks (`Invalid hook call`). Standard `npm install` hoists React and is fine. Monorepos, pnpm without hoist, and `file:`/`link:` deps may require explicit deduping by the consumer. The README's "Invalid hook call — duplicate React" section documents fixes. | ||
|
|
||
| ## Build output | ||
|
|
||
| | File | Format | Notes | | ||
| |------|--------|-------| | ||
| | `dist/index.esm.js` | ESM | Has `'use client'` banner | | ||
| | `dist/index.js` | CJS | Has `'use client'` banner | | ||
| | `dist/index.d.ts` | TS declarations | Generated by `rollup-plugin-dts` | | ||
| | `dist/webcomponent.js` | IIFE | React + ReactDOM bundled in, registers `<stackone-hub>` | | ||
|
|
||
| Pre-existing build warnings about `crypto` / `vm` Node built-ins (from `@stackone/utils` and `jsonpath-plus`) are noisy but harmless for the React bundles — only the webcomponent IIFE actually needs them and they're never reached at runtime in a browser. | ||
|
|
||
| ## Dev sandboxes | ||
|
|
||
| Both sandboxes are now their own npm packages and consume the hub via `"@stackone/hub": "file:../.."`, so they exercise the actual built `dist/` (including the `'use client'` banner). Edit hub source, run `npm run build` from the repo root, and both sandboxes pick up the new bundle automatically — no reinstall needed. | ||
|
|
||
| - **Vite sandbox** (`dev/vite/`): port 3001. Run via `npm run dev` from the repo root. First-time setup is `npm run dev:setup` (builds the hub, then `npm install` inside `dev/vite/`). | ||
| - **Next.js sandbox** (`dev/nextjs/`): port 3002. Run via `npm run dev:nextjs`. First-time setup is `npm run dev:nextjs:setup`. Use this one to validate SSR behaviour. | ||
|
|
||
| **Vite sandbox needs `resolve.dedupe`** for `react`, `react-dom`, `react-hook-form` because the symlinked hub at `node_modules/@stackone/hub` resolves to a directory with its own `node_modules` — two React copies otherwise. The `dev/vite/vite.config.ts` is already set up correctly. If you add a new peer dep to the hub, add it to the dedupe list. | ||
|
|
||
| If you add a new public prop to `StackOneHub`, update both `dev/vite/main.tsx` and `dev/nextjs/app/HubWrapper.tsx` so the props stay testable in both sandboxes. | ||
|
|
||
| ## Versioning / publishing | ||
|
|
||
| Releases use `release-please-config.json`. Don't bump `version` in `package.json` manually — release-please handles it. | ||
|
|
||
| ## Style conventions | ||
|
|
||
| - No comments in code unless the *why* is genuinely non-obvious. Names should carry the intent. | ||
| - TypeScript strict mode is on; don't reach for `any`. Internal types live in `src/types/`, feature-specific types live in their feature folder (`src/modules/integration-picker/types.ts`). | ||
| - 4-space indent (Biome enforces). | ||
| - Imports are sorted by Biome — let the formatter do it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| STACKONE_API_KEY= | ||
| ORIGIN_OWNER_ID=dummy_customer_id | ||
| ORIGIN_OWNER_NAME=dummy_customer_name | ||
| ORIGIN_USERNAME=dummy_customer_username | ||
| NEXT_PUBLIC_API_URL=http://localhost:4000 | ||
| NEXT_PUBLIC_APP_URL=http://localhost:3000 | ||
|
adefreitas marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| node_modules | ||
| .next | ||
| .env | ||
| .env*.local | ||
| next-env.d.ts.bak |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| 'use client'; | ||
|
|
||
| import { StackOneHub } from '@stackone/hub'; | ||
| import { useState } from 'react'; | ||
|
|
||
| interface HubWrapperProps { | ||
| initialToken: string; | ||
| apiUrl: string; | ||
| appUrl: string; | ||
| } | ||
|
|
||
| export default function HubWrapper({ initialToken, apiUrl, appUrl }: HubWrapperProps) { | ||
| const [token, setToken] = useState(initialToken); | ||
| const [manualToken, setManualToken] = useState(''); | ||
| const [theme, setTheme] = useState<'light' | 'dark'>('light'); | ||
|
|
||
| return ( | ||
| <div className={theme}> | ||
| <div style={{ display: 'flex', gap: 8, marginBottom: 12 }}> | ||
| <input | ||
| style={{ flex: 1, padding: 6, border: '1px solid #ccc', borderRadius: 4 }} | ||
| type="text" | ||
| placeholder="Paste a connect session token" | ||
| value={manualToken} | ||
| onChange={(e) => setManualToken(e.target.value)} | ||
| /> | ||
| <button onClick={() => setToken(manualToken)} disabled={!manualToken}> | ||
| Use token | ||
| </button> | ||
| <button | ||
| aria-label={ | ||
| theme === 'light' ? 'Switch to dark theme' : 'Switch to light theme' | ||
| } | ||
| onClick={() => setTheme((t) => (t === 'light' ? 'dark' : 'light'))} | ||
| > | ||
| {theme === 'light' ? '🌞' : '🌚'} | ||
| </button> | ||
| </div> | ||
| {token ? ( | ||
| <p style={{ fontSize: 12, color: '#666', wordBreak: 'break-all' }}> | ||
| Token: {token} | ||
| </p> | ||
| ) : ( | ||
| <p style={{ fontSize: 12, color: '#666' }}> | ||
| No token yet — set <code>STACKONE_API_KEY</code> in <code>.env</code> for | ||
| server-side fetching, or paste one above. | ||
| </p> | ||
| )} | ||
| <StackOneHub | ||
| key={token} | ||
| mode="integration-picker" | ||
| token={token || undefined} | ||
| baseUrl={apiUrl} | ||
| appUrl={appUrl} | ||
| theme={theme} | ||
| onSuccess={(account) => { | ||
| alert(`success: ${JSON.stringify(account)}`); | ||
| }} | ||
| /> | ||
| </div> | ||
| ); | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.