4 ms·
I developed the JS architecture of https://www.productreview.com.au https://www.productreview.com.au and we have faced tons of issues getting SSR right, but it
by eliseumds 7y ago
I developed the JS architecture of https://www.productreview.com.au https://www.productreview.com.au and we have faced tons of issues getting SSR right, but it was worth it. We're getting more than 10M pageviews a month and I'd like to share our experience:
* Upgrading NodeJS indeed gives us massive performance boosts, but apply with caution. Ideally have a set of visual regression tests just to be safe
* Profile your NodeJS code just like you do with browser code. Sometimes the bottleneck could be in an Express middleware or in reading a massive Webpack manifest file
* If a component doesn't need to rendered on the server, don't do it. Don't waste CPU cycles (for ex, out-of-view content). Just make sure you got your SEO meta tags right
* Don't load more data than you need. It takes time to parse, it takes time to loop through and it takes time to stringify for rehydration
* Enable BabelJS's debug mode and remove unnecessary plugins
* Don't import more stuff than you need. Tree-shaking is important on the server-side too
* If you're using CSS modules, use the Webpack loader `css-loader/locals` on the server so that it doesn't emit CSS files (useless). The client compiler should do so
* Monitor your server-to-server requests. They're usually what take the longest, so cache the most important ones
* As with the majority of websites, cache is king
* Properly serialize your JSON strings. That's what we use: https://gist.github.com/eliseumds/6192135660267e2c64180a8a9cdb7dd1 https://gist.github.com/eliseumds/6192135660267e2c64180a8a9c...
* It can be worth it to return a dangerous HTML string from a component instead of a tree of React nodes. We do that when we render SVGs and microdata tags
Again, it's a pain-in-the-butt. You'll have checksum errors, need to synchronize clock, polyfill Intl APIs because they're inconsistent and so on.
- minitech 7y ago> * Properly serialize your JSON strings. That's what we use: https://gist.github.com/eliseumds/6192135660267e2c64180a8a9c.. https://gist.github.com/eliseumds/6192135660267e2c64180a8a9c.... That doesn’t look like “properly”. The double escaping is overcomplicated and no safer compared to a direct window.__productreview_data = ${escapedReduxStateJsonString}; (and forgets about \v, maybe others), the transformation doesn’t preserve “</_escaped_script”, and it doesn’t address a vulnerability involving <!-- that’s contrivable. Closer to correct: JSON.stringify(data) .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029') .replace(/</g, '\\x3c') Better, if you put the JSON in an inert <script> (type="application/json"), it’s only necessary to escape < (or /<[/!]/g). This is a good idea so you can use restrictive CSPs.
- eliseumds 7y agoThanks for pointing out the `<!--` vulnerability. In regards to rendering the string inside a JSON.parse, we do that because of performance: https://v8.dev/blog/cost-of-javascript-2019 https://v8.dev/blog/cost-of-javascript-2019. From what I remember, we had some issues with IE11, thus the replacement for the other characters. We'll consider "application/json", makes sense.
- minitech 7y agoGiven a correct function that converts a JSON-representable value to embed-safe JSON, you can use it on the JSON to get your JSON.parse performance: const inlineJSON = data => JSON.stringify(data) .replace(/\u2028/g, '\\u2028') .replace(/\u2029/g, '\\u2029') .replace(/</g, '\\x3c'); with: const escapedReduxStateJsonString = inlineJSON(JSON.stringify(data)); But yeah, the isolated <script> thing is usually even better (more compact in addition to the security benefit).
- andrewingram 7y agoI've been recommended this library rather than figuring it for myself: https://github.com/yahoo/serialize-javascript https://github.com/yahoo/serialize-javascript You can use it in JSON mode like this: `serialize(obj, {isJSON: true});`