This issue was originally identified in voxpelli/voxpelli.github.com#32
Problem
DomStack's default layout uses htm/preact combined with preact-render-to-string for server-side HTML generation. This works well for Preact users, but introduces a Preact dependency that is unnecessary for sites that only need server-side HTML rendering and have no client-side component model.
The async-htm-to-string package provides a lightweight alternative: same htm tagged template syntax but renders directly to HTML strings without a virtual DOM layer. Users wanting this approach must currently discover and wire up async-htm-to-string entirely on their own, with no guidance from DomStack documentation or tooling.
Key differences from the Preact approach
| htm/preact | async-htm-to-string |
|---|
| Attributes | className, htmlFor, tabIndex | class, for, tabindex |
| Return value | Preact VNode | Promise<string> |
| Extra step | renderToString(html\...`)` | await html\...`` |
| Dependencies | preact, preact-render-to-string | async-htm-to-string only |
Gotchas users need to know
1. Standard HTML attributes, not React aliases
Using className with async-htm-to-string produces className="foo"literally in the HTML output — an invalid attribute that browsers ignore silently. No warning or error.
2. rawHtml() XSS risk
async-htm-to-string provides rawHtml() for injecting pre-rendered HTML strings without escaping. This is a direct XSS vector if used with user-controlled content. It should be treated like innerHTML assignment — only use for content you control or have explicitly sanitized.
3. Silent [object Object] failure
The html function returns an HtmlMethodResult object, not a plain string. If returned without await, downstream code produces [object Object] literally in the HTML output — no error, no warning, just broken output:
// BUG: Missing awaitexportdefaultfunction({ vars }){returnhtml`<h1>${vars.title}</h1>`;// Returns object, not string}// CORRECTexportdefaultasyncfunction({ vars }){returnawaithtml`<h1>${vars.title}</h1>`;}Proposed solution
Option A (minimum): Document as recommended Preact-free alternative
Add a section to DomStack documentation that:
- Explains when to use it (SSR-only sites without client Preact components)
- Shows the migration path from
htm/preact + preact-render-to-string - Documents the three gotchas above
- Provides a helper pattern for composing partials with
rawHtml()
This requires no code changes to DomStack itself.
Option B (follow-on): Built-in page.htm.js page type
A new page builder that recognizes *.page.htm.js files and automatically wraps the return value — eliminating the [object Object] failure mode and reducing boilerplate to zero imports.
Recommendation: Option A immediately, Option B upstream once demand is validated.
Problem
DomStack's default layout uses
htm/preactcombined withpreact-render-to-stringfor server-side HTML generation. This works well for Preact users, but introduces a Preact dependency that is unnecessary for sites that only need server-side HTML rendering and have no client-side component model.The
async-htm-to-stringpackage provides a lightweight alternative: samehtmtagged template syntax but renders directly to HTML strings without a virtual DOM layer. Users wanting this approach must currently discover and wire upasync-htm-to-stringentirely on their own, with no guidance from DomStack documentation or tooling.Key differences from the Preact approach
htm/preactasync-htm-to-stringclassName,htmlFor,tabIndexclass,for,tabindexrenderToString(html\...`)`await html\...``preact,preact-render-to-stringasync-htm-to-stringonlyGotchas users need to know
1. Standard HTML attributes, not React aliases
Using
classNamewithasync-htm-to-stringproducesclassName="foo"literally in the HTML output — an invalid attribute that browsers ignore silently. No warning or error.2.
rawHtml()XSS riskasync-htm-to-stringprovidesrawHtml()for injecting pre-rendered HTML strings without escaping. This is a direct XSS vector if used with user-controlled content. It should be treated likeinnerHTMLassignment — only use for content you control or have explicitly sanitized.3. Silent
[object Object]failureThe
htmlfunction returns anHtmlMethodResultobject, not a plain string. If returned withoutawait, downstream code produces[object Object]literally in the HTML output — no error, no warning, just broken output:Proposed solution
Option A (minimum): Document as recommended Preact-free alternative
Add a section to DomStack documentation that:
htm/preact+preact-render-to-stringrawHtml()This requires no code changes to DomStack itself.
Option B (follow-on): Built-in
page.htm.jspage typeA new page builder that recognizes
*.page.htm.jsfiles and automatically wraps the return value — eliminating the[object Object]failure mode and reducing boilerplate to zero imports.Recommendation: Option A immediately, Option B upstream once demand is validated.