@dooboostore/simple-boot-http-server-ssr

NPM versionBuild and TestLicense: MIT

@dooboostore/simple-boot-http-server-ssrprovides a powerfulServer-Side Rendering (SSR)environment by seamlessly integrating@dooboostore/simple-boot-frontand@dooboostore/simple-boot-http-server.

It shipsfive interchangeable rendering strategiesas separateFilters, so you pick the one that fits your app instead of being locked into one DOM backend:

FilterDOM backendInstance reuseBest for
SSRFilterjsdomPooled (min/max, generation-based eviction)High-traffic apps that want warm instances ready to render
SSRDomParserFilter@dooboostore/dom-parser(this monorepo's own lightweight, dependency-free DOM)Fresh window per requestLower memory footprint, no jsdom dependency; pairs with the AOP-based data-hydration proxy below
SSRLinkDomFilterlinkedomPooled (poolOptionmin/max, generation-based eviction)Alternative lightweight DOM backend
SSRSimpleWebComponentDomParserFilter@dooboostore/dom-parserFresh window per request@dooboostore/simple-web-componentapps without a browser;intentServicesinjection and SWC@propertyhydration (below)
SSRSimpleWebComponentFilterReal headlessChromium via PlaywrightNew browser context per request (browser instance itself is reused)Pixel/spec-perfect rendering (declarative Shadow DOM, real browser APIs) for@dooboostore/simple-web-componentapps, at the cost of spinning up a real browser

All of them execute your existing frontend code (routing, components, services) on the server and return fully rendered HTML — no separate server-only rendering path to maintain.


🚀 Key Features

  • Seamless SSR: Render your SPA on the server without code modifications to maximize SEO and First Contentful Paint (FCP).
  • Pluggable DOM backend: Choosejsdom, this monorepo's own@dooboostore/dom-parser,linkedom, or a real headless Chromium (Playwright) — swap theFilterwithout touching your app code.
  • Component/Instance Pooling(SSRFilter): Optimize resource management by poolingjsdom-backed SSR instances (withmin/maxpool size and generation-based stale eviction) to handle concurrent requests efficiently.
  • Automatic Data Hydration:SSRDomParserFilterDataHydrationProxytransparently wraps@Sim-registered service methods — on the server it caches the method's result keyed bysymbol.method(args), embeds it in the HTML, and on the client it's read back instead of re-fetching, so hydration requires no manual wiring in your services.
  • Isomorphic Architecture: Maintain a single codebase for routing, services, and components shared between server and client.
  • SWC-native rendering:SSRSimpleWebComponentFilterdrives a real Chromium instance via Playwright so@dooboostore/simple-web-componentapps (declarative Shadow DOM, native Custom Elements) render exactly as they would in a real browser.

📦 Installation

pnpm add @dooboostore/simple-boot-http-server-ssr reflect-metadata# jsdom, linkedom, and playwright are regular dependencies; for SSRSimpleWebComponentFilter also run:npx playwright install chromium

💻 Core Usage

1. Universal Bootfactory (bootfactory.ts)

Define a shared function to initialize the SWC app on both server and client.

importswcRegister, {typeSwcAppInterface}from'@dooboostore/simple-web-component';exportdefaultasync(window:Window,otherInstanceSim?:Map<symbol,any>,urlPath?:string) => {// Register the built-in SWC elements (swc-app, ...) on this windowawaitswcRegister(window);constapp =window.document.querySelector('#app')asSwcAppInterface;awaitapp.connect({window,path: urlPath ??'/',routeType:'path',
    otherInstanceSim,// e.g. the `sim` Map from registerComponentsonStartedLazyDefineComponent: [/* component/page factories */]
  });return{ app };
};

2. Backend Configuration (SSR Filter)

Add one of the SSR filters to your server configuration. Example usingSSRSimpleWebComponentDomParserFilter:

constssrFilter =newSSRSimpleWebComponentDomParserFilter({frontDistPath:'./dist-client',frontDistIndexFileName:'index.html',// Services exposed to SSR: each request gets them with its own `rr` injected, passed as `sim`intentServices: [/* SimConfig items with a symbol */],registerComponents:async(window:any,rr:RequestResponse,sim:Map<symbol,any>) => {const{ app } =awaitbootfactory(window, sim,UrlUtils.getUrlPath(window.location));returnapp;// returning the SwcAppInterface enables @property hydration},ssrExcludeFilter:rr=>/\.(js|css|map|ico|png|json)$/.test(rr.reqUrlPathName)
});constserver =newSimpleBootHttpSSRServer(newHttpSSRServerOption({listen: {port:8080},filters: [newResourceFilter('./dist-client', ['/bundle.js',/\.map$/]), ssrFilter]
  })
);
server.run();

SSRSimpleWebComponentFilter(real Chromium via Playwright) takes the samefrontDistPath/frontDistIndexFileName/ssrExcludeFilterplusplaywright: { waitUntil, waitForSelector, waitForTimeout, timeout, ignoreTags }. Playwright loads your real bundle, so Custom Elements register themselves andregisterComponentsis usually unnecessary.

For@dooboostore/simple-boot-frontapps, useSSRDomParserFilter(orSSRFilterforjsdom/SSRLinkDomFilterforlinkedom, both pooled viapoolOption: { min, max, clearIntervalTime? }) — sameFilterinterface:

constdomParserFilter =newSSRDomParserFilter({frontDistPath:'./dist-client',frontDistIndexFileName:'index.html',factorySimFrontOption:(window) =>newSimFrontOption({window/* ... */}),factory: mySimpleBootHttpSSRFactory,using: [/* components/pages to register */]
});constserver =newSimpleBootHttpSSRServer(newHttpSSRServerOption({filters: [newResourceFilter('./dist-client'), domParserFilter]
  })
);
server.run();

🌊 Data Hydration

SSRDomParserFilterpairs withSSRDomParserFilterDataHydrationProxy(@Sim-injected, wraps@Sim-registered service methods) to automate transferring async data from server to client with no manual wiring in your services:

  1. Server Side: The first call to a wrapped service method runs normally, then its result is cached viasimpleBootFront.saveDataHydration(key, data)(key=<service symbol>.<method>(<args>)) and serialized into the HTML.
  2. Client Side: On hydration, the same call is intercepted andsimpleBootFront.cutDataHydration(key)returns the cached value instead of re-running the (async) call.
  3. Result: The client shows the exact data the server already rendered, with no redundant fetch.

SSRFilter/SSRWorker(thejsdom-based filters) use a simpler, manual mechanism instead: whatever you assign ontowindow.server_side_dataon the server is serialized as an inline<script>and available onwindow.server_side_dataon the client.

SWC@propertyhydration (SSRSimpleWebComponentDomParserFilter)

  1. registerComponentsreturns theSwcAppInterface(host +connectedElements()).
  2. @propertymetadata is collected from the host and connected elements into{ sel, prop, value }items (sel=[swc-use-ssr="<id>"], JSON-serializable values only).
  3. Embedded as a<script>that runs before the bundle: finds each node withquerySelectorand setsel[prop] = value.
  4. The JSON is escaped for inline scripts (<→\u003c, U+2028/U+2029), so a value containing</script>can't break out of the tag.JSON.parserestores it exactly.
  5. Works because a custom element keeps own properties setbeforecustomElements.define— the upgrade doesn't reset them. Declare hydrated fields withdeclare(see the SWC README).

📖 Learn More

Check out the detailed guides and tutorials in thedocumentfolder.


License

MIT License