fix(react): avoid bundling hydrate module into client graph#797
Merged
OS-jacobbell merged 2 commits intoMay 12, 2026
Merged
Conversation
Contributor
|
closes #766 |
1e2eb0b to
aa2b9df
Compare
Contributor
|
For what it's worth, I've also tested this on my side and can confirm that this prevents the hydrate bundle being sent to the client @davidpett. Would love to see this make it in as well 👍 |
OS-jacobbell
approved these changes
May 12, 2026
Contributor
OS-jacobbell
left a comment
There was a problem hiding this comment.
Thanks for the PR, looks like the right solution!
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Pull request checklist
Pull request type
What is the current behavior?
In the runtime-based Next.js SSR setup, the Stencil hydrate module (output of `dist-hydrate-script`) ends up in the client bundle and is fetched by the browser on every page load, even though it is only ever meant to run on the server.
The generated `components.server.ts` file starts with `'use client'` and contains, once per component:
```ts
hydrateModule: import('component-library/hydrate') as Promise,
```
Because this sits at module top level inside a `'use client'` module, webpack compiles the dynamic import into an eager chunk request (`webpack_require.e("..._hydrate_index_mjs")`) that fires during module initialization on the client. The `typeof window !== 'undefined'` guard inside `createComponent` short-circuits execution of the hydrate path on the client, but runs too late to prevent the chunk fetch. The hydrate module is downloaded and evaluated in the browser even though none of its exports are ever called there.
Observable via `ANALYZE=true next build` in `example-project/next-15-runtime-based` (hydrate chunk appears in `.next/analyze/client.html`) and via DevTools Network tab on `next start` (a request for `_app-pages-browser_component-library_hydrate_index_mjs.js` fires on every page that imports from `component-library-react/next`).
Issue URL: n/a (reported via internal discussion)
What is the new behavior?
Verification on `next build` in `example-project/next-15-runtime-based`:
SSR markup verified intact via `curl`: `<template shadowrootmode="open">` and serialized complex props (Map, Set, Symbol, Infinity) still render on the server side.
All 41 React output-target unit tests pass.
Does this introduce a breaking change?
`createComponent`'s `hydrateModule` option type is widened from `Promise` to `Promise | undefined`. This is a non-breaking widening (accepting `undefined` where it previously required a Promise is strictly more permissive) and direct callers outside the generator are extremely rare.
Other information
Follow-up worth considering (out of scope for this PR):