Server Side Rendering (SSR)

Usage

This brand new SSR package currently provides three different interfaces to pre-render web components:

Option 1. via the CLI (super helpful for testing).

This Node.js CLI currently accepts two arguments: 1. The string of HTML to pre-render 2. Boolean to conditionally enable/disable syntax highlighting (which defaults to true when using the CLI directly, false when being used for PHP rendering)

$ node packages/servers/ssr-server/cli.js '<bolt-button color="secondary">SSR Test</bolt-button>'

This returns back the pre-rendered, hydration-friendly markup (with or without syntax highlighting)

image

Option 2. via Twig filter / function

There’s also two ways to use this SSR functionality in Twig via the new bolt_ssr Twig filter and bolt_ssr Twig function.

Both of these also use the Node.js CLI interface under the hood with the added bonus of returning back the original pre-rendered HTML as a fallback.

{% filter bolt_ssr %}
  <bolt-button color=“secondary”>
    SSR Test
  </bolt-button>
{% endfilter %}

Option 3. via JS using the SSR library itself (experimental)

This SSR functionality can even be used in other JS files via the renderToString method on the @bolt/ssr-server package.

Note: currently this method currently requires the developer to manually call the shutDownSSRServer method afterwards — hopefully something we can automate in the next iteration.

Misc Notes

  • The first couple of implementations of this were powered by a Google Puppeteer instance however this latest iteration managed to swap out Puppeteer for a lighter weight JSDOM implementation that’s been lightly polyfilled! 🎉
  • SSR compile times are averaging around 1 to 1.3 seconds (when no Webpack build is required). Quick enough for a larger POC but not quick enough to globally roll this out without expensive compile times on the server

Gotchas / Next Steps

  • Auto-wire Components Waiting To Load: we should figure out how we want to create a list of the custom element tags that need to be defined before the SSR logic kicks off: https://github.com/bolt-design-system/bolt/blob/cd72b43574e7d33810c80147edc8cb1613048a38/packages/servers/ssr-server/renderer.js#L42
  • Cache Busting: Further work needed to get cache busting added to the SSR build process
  • Async Rendering: Look into parallelizing / making requests more async
  • Alt Interface for PHP Integtation? We should look into using web sockets or even using a vanilla server request / response interface.
  • Caching: making the same request to the same component with the same data on the same version of Bolt should pull straight from memory to speed up build times.
  • Compare SSR Perf: Further research into performance optimizations -- including doing a benchmark between say, SSR for a Vue.js component compared to our current implementation
  • Test CKEditor Integration: Test integrating CKEditor example content with this new SSR functionality
  • Combine Webpack Builds? Look into (potentially) combining the client-side vs server-side webpack builds to eliminate having to compile some of the same components twice
  • Research Further Optimizing Client-side Perf: Lighter weight JS load in the client = faster SSR response times
  • Combine Multiple Servers: Out of simplicity, we currently have 3 different Express servers -- the default Express server, the testing server when running Jest tests, and now this new SSR server. At a minimum I could see two of these getting merged together into a single server instance (if not merging together all three)
  • Handle Pre-rendered HTML: the current implementation doesn't know if the markup provided (or pieces of the markup provided) has been pre-rendered which could lead to unintended rendering quirks and/or extra unnecessary processing
  • Fragment Cache? If a button component was being pre-rendered but contained an already pre-rendered icon component, how might we handle that?

Performance Impact

Notes:

  1. This is based on the current implementation which will reuses the bolt-global.server.js if a Webpack build had already been done -- this speeds up the initial SSR request.
  2. CLI benchmarks were are based on this bolt-global.server.js file already existing
  3. The current implementation is synchronous -- only one request at a time and PHP waits for the request to complete before continuing.

CLI Performance (per compile):

  • Without formatting HTML or syntax highlighting: 1.08s average (or 0.05s faster on average than with Prettier formatting)
  • With highlighting + prettier: 1.23s average
  • Without syntax highlighting, with HTML formatting: 1.13s average

Pattern Lab Compile Times (Prod) with SSR enabled (Select Usage)

  • Using it in a handful of places (~7 places): ~21.97s total compile time
  • Without SSR enabled: ~13.s total compile time

Global Usage (Tests SSR <bolt-button> Globally)

  • Pre-rendering all <bolt-button> components in the static site generator: ~1 minute, 40s compile time (vs 4s compile time normally)
  • Pre-rendering all <bolt-button> components in Pattern Lab: 11 minutes, 27.7s minutes (vs 13.2s normally)