How do we deal with the performance issue findings in our components-react package? #7295
Replies: 3 comments 3 replies
tl;drAddressing the following issue, could potentially fix at least "Root Cause 2" and "Root Cause 3" (if not all three), without changing anything else: I suggest to test that on the POW repo! @josemanosalvas if you or someone from your team would have time to help me set up the repo and tell my how to trigger dependency graph tests, that'd be great! Root Cause 1 — Barrel Exports and Side Effects
Root Cause 2 — crypto-browserify Polyfill
If this does not help:
Root Cause 3 — Monolithic Hydrate Bundle
|
Further investigations brought me to the following findings:POW is seeing
Possible solutions1️⃣ Function wrapperIf we would wrap all export const PostAccordion = createComponent({
tagName: 'post-accordion',
hydrateModule: () => import('@swisspost/design-system-components/hydrate'),
...
});But wait... to make this work the usage of // @stencil/react-output-target/dist/ssr.js
const V = (e) => {
...
return async (t) => {
let r = false;
let f;
if (!f) {
f = await e.hydrateModule() // <-- before: `e.hydrateModule`
r = true);
}
...
};
};
export {
V as createComponent
};Conclusion Both updates must be done either in a thirdparty node module ( We could open tickets on the stencil repos, to ask for implementation, but sadly, with this solution (which would be best for tree shaking) the bundle analyzer, did not detect the hydrate module anymore, neither in the client, nor in the server bundle. It seems the bundler analyzer is unable to detect whether a module is actually loaded/used. 2️⃣ Export with side-effect markerIn our own package {
...
"exports": {
"./post-components.css": "./dist/post-components.css",
".": {
"types": "./dist/types/index.d.ts",
"import": "./dist/index.js",
"node": "./dist/index.server.js",
"sideEffects": false // ← Tell bundler: no forced deps
},
"./server": {
"types": "./dist/types/index.server.d.ts",
"import": "./dist/index.server.js",
"sideEffects": false
},
"./icons": {
"types": "./dist/types/icons-generated/index.d.ts",
"import": "./dist/icons-generated/index.js",
"node": "./dist/icons-generated/index.js",
"sideEffects": false
},
"./icons/*": {
"types": "./dist/types/icons-generated/*.d.ts",
"import": "./dist/icons-generated/*.js",
"node": "./dist/icons-generated/*.js",
"sideEffects": false
}
},
}Conclusion Doing this, I ended up with the same issue as in solution 1️⃣. The bundler analyzer did not show the hydrate module in the client and the server bundle anymore. |
|
| Entrypoint | Server Component | Client Component | Comment |
|---|---|---|---|
@swisspost/design-system-components-react/{component} |
Receives server components. 1 | Receives client components. 2 | This is the context-aware entry point (bundler picks automatically). |
@swisspost/design-system-components-react/server/{component} |
Receives server components. 1 | Receives server components. 1 | This is the explicit server entry point (always includes hydrate module, even in client bundle). |
1 Server Components: Including the hydrate module for declarative shadow DOM creation during SSR.
2 Client Components: Does NOT include the hydrate module. Can be used during SSR, but won't output declarative shadow DOM.
Why the hydrate module only works in Server Components
Server Components never ship code to the browser. They execute on the server, produce HTML, and stream it to the client. The hydrate module renders web components to declarative shadow DOM during this process — then is discarded. Zero client-side cost.
Client Components share a single module graph between SSR and browser. Next.js bundles Client Components once and uses that bundle for both server-side rendering and client-side hydration. There is no mechanism — in Turbopack or webpack — to resolve a different module for the SSR pass vs the browser pass of a Client Component.
Including the hydrate module in a Client Component has two consequences:
-
Bundle bloat. The hydrate module (Stencil's full renderer) ships to the browser where it serves no purpose — web components self-initialize on the client via
defineCustomElement, not by using the hydrate module. -
Hydration mismatch risk. If the hydrate module produces different HTML than what the client-side render expects (declarative shadow DOM markup,
s-idattributes, serialized styles), React throws hydration errors. Both passes must produce identical output — which means either both include the hydrate module, or neither does.
Declarative Shadow DOM in a Client Component
This is currently not possible without shipping the hydrate module to the browser. Three options:
Option 1: Accept the cost
Import from ./server. The hydrate module ships to the client bundle as dead weight — @stencil/ssr delegates to clientModule in the browser, so the hydrate module is unused but bundled.
"use client";
import { PostStepper } from "@swisspost/design-system-components-react/server";
// ✅ Declarative shadow DOM during SSR
// ⚠️ Hydrate module included in client bundleOption 2: Skip declarative shadow DOM
Import from the main entry point. The custom element tag is rendered during SSR (light DOM children remain visible), shadow DOM initializes after JavaScript executes. Acceptable for interactive components behind user action.
"use client";
import { PostStepper } from "@swisspost/design-system-components-react";
// ❌ No declarative shadow DOM during SSR
// ✅ No hydrate module in client bundleOption 3: Restructure with composition
Render the web component in a Server Component and pass it as children to the Client Component. Only viable if the web component needs no reactive client state (no useState-driven props, no onClick handlers).
// page.tsx (Server Component)
import { PostIcon } from "@swisspost/design-system-components-react";
import { ClientWrapper } from "./client-wrapper";
export default function Page() {
return (
<ClientWrapper>
<PostIcon name="1001-pen" /> {/* SSR'd with declarative shadow DOM */}
</ClientWrapper>
);
}// client-wrapper.tsx
"use client";
export function ClientWrapper({ children }: { children: React.ReactNode }) {
// Client-side logic here
return <div>{children}</div>;
}Proof of work
Tested with #7660, which implements the above changes






Uh oh!
There was an error while loading. Please reload this page.
The POW team found some performance issues with our components-react package.
See: Performance-Analysis-Findings
We're starting this thread so we can discuss about the topic and find out where and how we can improve the package.
All reactions