Prerequisites
Stencil Version
4.43.5
Current Behavior
When using Angular SSR and StencilJS components, I would expect the server to generate the same HTML markup as what it generates in the browser.
Example markup generated on the server:
<my-button ngh="0" class="my-button hydrated" role="button" tabindex="0" s-id="5">
<!--r.5-->
<!--o.0.5-->
<!--s.5.0.0.0.-->
<!--t.0.5-->
Hello
</my-button>
Expected Behavior
The server-rendered version is including extra HTML comments that don't exist in the browser-rendered version, causing hydration errors and causing the page to be unusable.
Example markup generated on the browser:
<my-button ngh="0" class="my-button hydrated" role="button" tabindex="0" s-id="5">
<!--r.5-->
<!--s.5.0.0.0.-->
Hello
</my-button>
System Info
System: node 24.15.0
Platform: darwin (25.5.0)
CPU Model: Apple M4 Max (14 cpus)
Compiler: /Users/my-user/development/my-repo/node_modules/.pnpm/@stencil+core@4.43.5/node_modules/@stencil/core/compiler/stencil.js
Build: 1779994050
Stencil: 4.43.5 🍐
TypeScript: 5.8.3
Rollup: 4.44.0
Parse5: 7.2.1
jQuery: 4.0.0-pre
Terser: 5.37.0
Steps to Reproduce
This is hard to give reproduction for due to us having a separate repository for the components + react + angular component projects that we use to distribute these, however, I will give you as much info as possible:
Here is our stencil.config.ts:
export const config: Config = {
namespace: "component-lib",
watchIgnoredRegex: [/readme\.md$/],
hashFileNames: false,
devServer: {
openBrowser: false,
reloadStrategy: "pageReload",
},
plugins: [sass()],
globalStyle: "src/global.scss",
outputTargets: [
{
type: "dist",
esmLoaderPath: "../loader",
copy: [
{
src: "../node_modules/@accesso/icons/content/webfonts",
dest: "../webfonts",
},
],
},
{
type: "dist-custom-elements",
customElementsExportBehavior: "auto-define-custom-elements",
externalRuntime: false,
},
{
type: "dist-hydrate-script",
dir: "./dist/hydrate",
},
{
type: "docs-json",
file: "./custom-elements.json",
},
{
type: "www",
serviceWorker: null, // disable service workers
copy: [{ src: "components/*/*.html", dest: "pages" }],
},
angularOutputTarget({
esModules: true,
componentCorePackage: "@accesso/component-lib",
outputType: "standalone",
directivesProxyFile: "../angular-workspace/projects/ng-component-lib/src/lib/stencil-generated/components.ts",
directivesArrayFile: "../angular-workspace/projects/ng-component-lib/src/lib/stencil-generated/index.ts",
valueAccessorConfigs: [
{
elementSelectors: ["quantity-stepper"],
event: "valueChange",
targetAttr: "value",
type: "number",
},
],
}),
reactOutputTarget({
esModules: true,
outDir: "../react-component-lib/lib/components/stencil-generated/",
}),
],
};
When we build, we then have our @accesso/component-lib (web components), @accesso/ng-component-lib (Angular output), and @accesso/react-component-lib.
In our Angular repository, we do the following in app.config.ts:
export const appConfig: ApplicationConfig = {
...other configs...
provideAppInitializer(() => {
return import('@accesso/ng-component-lib/loader').then(({ defineCustomElements }) => defineCustomElements());
}),
}
And in our app.config.server.ts file, we do:
// Follow how Ionic hydrates their Angular server components
export function hydrateComponents(doc: Document, appId: string) {
return () => {
return hydrateDocument(doc, {
excludeComponents: [
// overlays
'adk-alert',
],
}).then((hydrateResults) => {
hydrateResults.diagnostics.forEach((d) => {
if (d.type === 'error') {
console.error(d.messageText);
} else if (d.type === 'debug') {
console.debug(d.messageText);
} else {
console.log(d.messageText);
}
});
if (doc.head != null) {
const styleElms = doc.head.querySelectorAll('style[data-styles]') as NodeListOf<HTMLStyleElement>;
// eslint-disable-next-line @typescript-eslint/prefer-for-of
for (let i = 0; i < styleElms.length; i++) {
styleElms[i]?.setAttribute('ng-transition', appId);
}
}
});
};
}
const serverConfig: ApplicationConfig = {
providers: [
... server providers ...
{
provide: BEFORE_APP_SERIALIZED,
useFactory: hydrateComponents,
multi: true,
deps: [DOCUMENT, APP_ID],
},
}
};
export const config = mergeApplicationConfig(browserConfig, serverConfig);
Code Reproduction URL
Too complicated, see my steps above.
Additional Information
The only way to fix this is to add the ngSkipHydration to all the Stencil components which is a huge headache.
Here's the Angular error in the browser console:
RuntimeError: NG0500: During hydration Angular expected a text node but found a comment node.
Angular expected this DOM:
#text(Hello) <-- AT THIS LOCATION
…
Actual DOM is:
<my-button class="my-button thydrated" role="button" tabindex="0">
<!-- r.3 --> <-- AT THIS LOCATION
…
</my-button>
Note: attributes are only displayed to better represent the DOM but have no effect on hydration mismatches.
To fix this problem:
* check the "_HomeComponent" component for hydration-related issues
* check to see if your template has valid HTML structure
* check if there are any third-party scripts that manipulate the DOM. More info: https://v21.angular.dev/guide/hydration#third-party-scripts-with-dom-manipulation
* or skip hydration by adding the `ngSkipHydration` attribute to its host node in a template
Find more at https://v21.angular.dev/errors/NG0500
at validateMatchingNode (_debug_node-chunk.mjs:6269:11)
at locateOrCreateTextNodeImpl (_debug_node-chunk.mjs:16298:16)
at Module.ɵɵtext (_debug_node-chunk.mjs:16279:22)
at HomeComponent_Template (home.component.ts:30:16)
at executeTemplate (_debug_node-chunk.mjs:5088:5)
at renderView (_debug_node-chunk.mjs:5488:7)
at renderComponent (_debug_node-chunk.mjs:5467:5)
at renderChildComponents (_debug_node-chunk.mjs:5517:5)
at renderView (_debug_node-chunk.mjs:5502:7)
at ComponentFactory2.createComponentRef (_debug_node-chunk.mjs:8764:7)
Prerequisites
Stencil Version
4.43.5
Current Behavior
When using Angular SSR and StencilJS components, I would expect the server to generate the same HTML markup as what it generates in the browser.
Example markup generated on the server:
Expected Behavior
The server-rendered version is including extra HTML comments that don't exist in the browser-rendered version, causing hydration errors and causing the page to be unusable.
Example markup generated on the browser:
System Info
System: node 24.15.0 Platform: darwin (25.5.0) CPU Model: Apple M4 Max (14 cpus) Compiler: /Users/my-user/development/my-repo/node_modules/.pnpm/@stencil+core@4.43.5/node_modules/@stencil/core/compiler/stencil.js Build: 1779994050 Stencil: 4.43.5 🍐 TypeScript: 5.8.3 Rollup: 4.44.0 Parse5: 7.2.1 jQuery: 4.0.0-pre Terser: 5.37.0Steps to Reproduce
This is hard to give reproduction for due to us having a separate repository for the components + react + angular component projects that we use to distribute these, however, I will give you as much info as possible:
Here is our
stencil.config.ts:When we build, we then have our
@accesso/component-lib(web components), @accesso/ng-component-lib(Angular output), and@accesso/react-component-lib.In our Angular repository, we do the following in
app.config.ts:And in our
app.config.server.tsfile, we do:Code Reproduction URL
Too complicated, see my steps above.
Additional Information
The only way to fix this is to add the
ngSkipHydrationto all the Stencil components which is a huge headache.Here's the Angular error in the browser console: