Join the conversation

Sign in to join this conversation, and others like it, in the communities you care about.

React

A community of developers, designers and others who love React.js. ⚛️

React / General

How we do streaming server-side rendering and caching for Spectrum

How we do streaming server-side rendering and caching for Spectrum

React/General · February 7, 2018 · 4:10pm

How we do streaming server-side rendering and caching for Spectrum

React / GeneralFebruary 7, 2018 · 4:10pm
Just published on the Zeit blog: https://zeit.co/blog/streaming-server-rendering-at-spectrum
Any questions about streaming SSR?
ZEIT – Streaming Server-Side Rendering and Caching at Spectrum
https://zeit.co/blog/streaming-server-rendering-at-spectrum

February 7, 2018 · 4:34pm

I was surprised to not see styled-components mentioned.

  • reply
  • like

My experience has been that streaming works well when styles have been extracted as part of the build process.

  • reply
  • like

But for CSS-in-JS you need to get the full response to send the styles first, otherwise there's a FOUC.

  • reply
  • like

There are "hacks" to get streaming working correctly with css-in-js. It requires interpolating style tags with the html being streamed as chunks are generated by react. see https://github.com/threepointone/glamor-stream

  • reply
  • like

That totally makes sense. Applying critical styles as you learn about them. Also, threepointone is a wizard.

  • reply
  • like

What is the advantage of SSR, if as mentioned in the article the data for unauthenticated users doesn't change and for authenticated users it's all client-side anyway?

  • reply
  • like

How are you using react-helmet-async?

  • reply
  • like

@mxstbr Spectrum is sill a combo of CRA and Backpack right?

  • reply
  • like

styled-components does exactly that @tk, we have a sheet.interleaveWithNodeStream API which interleaves style tags with the HTML as it gets along

  • reply
  • like

@jaredpalmer yep, backpack for all our backend servers

  • reply
  • like

@tushkiz yep, using react-helmet-async!

  • reply
  • like

@sedubois SEO, Spectrum needs to be 100% indexed on Google

  • reply
  • like

And no, client-side-only rendering is not enough to get indexed on search engines haha

  • reply
  • like

@mxstbr FYI, the article is missing the images for me

  • reply
  • like

@mxstbr great post! I have tried the interleaveWithNodeStream function. I have got the SSR working however, some of the styles were missing on the part of the UI that was not server-side rendered. I don't know if I am missing a piece or if it's a known problem.

  • reply
  • like

@hakimelek you have to call consolidateStreamedStyles as the very first thing in your app.

  • reply
  • like

Before you even import any

  • reply
  • like

Components

  • reply
  • like

Yes, I am doing that :/

  • reply
  • like

Want to submit a post in the styled-components community with your code @hakimelek? https://spectrum.chat/styled-components I'll see what I can do

  • reply
  • like

February 8, 2018 · 3:34am
  • reply
  • like

hi @mxstbr seems spectrum only does SSR in specific cases? cuz it does not when view-source

  • reply
  • like

been trying to get renderToNodeStream to work and ran into issue with Helmet and styled-components... about to give up then i ran into this thread...

  • reply
  • like

@mxstbr id you use SSR for SEO, why not just doing pre-rendering e.g using Gatsby?

  • reply
  • like

Just playing devil‘s advocate here, because as shown in the article, SSR brings a lot of complexity compared to a static site. So I’m trying to understand exactly what unique value you get from it.

  • reply
  • like

@egoist as I wrote in the article, as soon as you visit the page once we install a serviceworker which serves an app shell on any subsequent visit ☺️

like-fill
1
  • reply
  • like

@sdubois pre-rendering a real-time chat app? We have multiple tens of thousands of pages already, all of which need to be served with as close to real-time data as possible. How would we pre-render that?

  • reply
  • like

@mxstbr thanks, I hadn't understood what was meant by "public pages". I thought that the dynamic chat data was loaded client-side anyway.

  • reply
  • like

I haven't yet understood how the cache gets invalidated when a page's contents changes. It looks like the cache key is only `request.path`, however this doesn't change when the page contents change?

  • reply
  • like

@mxstbr ah that's brilliant

  • reply
  • like

@sdubois we don't do any invalidation right now 🙈 pages are cached for 30 minutes

  • reply
  • like

Working on that though, planning to have proper invalidation sometime soon

  • reply
  • like

Any ideas on how to key things so they can easily be invalidated when anything changes? Imagine a thread page like this one

  • reply
  • like

The issue is that a thread page like this one needs to be invalidated if:

1. A new message is sent

2. The contents are edited

3. Any participant changes their name or username or avatar

4. The community or channel name or image change

5. Somebody likes a message

  • reply
  • like

That's a lot of stuff to key by

  • reply
  • like

@mxstbr yeah. Note that instead of having a key that's unique for all those things, you could manually invalidate the cache whenever one of these things changes. But either way, that requires your data model to have relations between each of these points and the pages which are affected (but I guess it already has those relations in order to build the page contents).

One extra thought I had is that even with the caching, isn't the user is forced to load data from an origin server (on Now)? I.e. it can be a quite long trip introducing latency if someone is located far away from the origin server. Do you have some kind of server replication across different regions? Or would you consider doing CDN edge-level caching, e.g. with CloudFlare's new Workers beta feature? (https://www.cloudflare.com/products/cloudflare-workers/) (that would somehow require the edge caches to be informed about cache invalidation as well)

Additional question, why not using service workers for unauthenticated visitors as well? Might offer an extra perf improvement on top of what the article describes, and it could also solve this caching issue for returning visitors.

  • reply
  • like

February 9, 2018 · 11:33am

What sort of fallbacks do you have in place for browsers that don't support service workers ? I've found SSR to be a lot more of a mindf* when simultaneously dealing with auth and data fetching, so the service worker/app shell solution sounds appealing but I was under the impression it still wasn't supported in some browsers (IE) - is there a simple polyfill or do you fall back to a whole different method/exp for users who can't have the page served by SWs ?

  • reply
  • like

Sorry, that was addressed to @mxstbr

  • reply
  • like

I'm currently in the early stages of architecting something not too dissimilar from Spectrum (in the sense of also including chat/thread structures) and I wanted to try SSR with Next but handling auth flows has taken me a few days to wrap my head around (doesn't seem like there's a ton of info out there for how that works in Next, besides the barebones example implementations in the Next repo). While it doesn't cover auth, your article really couldn't have come at a more perfect time - so dense in useful information/techniques/ideas. So thanks a lot.

  • reply
  • like

@sedubois we can't use a ServiceWorker on first visit, folks have to visit the page once for it to install. :( Definitely investigating CDN's though, looking into Fastly mostly right now

  • reply
  • like

@mtphil well, browser that don't support ServiceWorkers just have the standard SPA experience, only their first open of the website will always be server rendered. No big deal!

  • reply
  • like

Ah, I see - I wasn't thinking about it in a progressive enhancement sort of paradigm (trying to make that my default mindset though), but that makes total sense. I'll have to try that out - never messed with SWs bfore but they seem like fun.

  • reply
  • like

February 11, 2018 · 7:05am

@mxstbr does Fastly allow to do auth at the edge? Or how do you plan to offload to the CDN edge without checking auth at the origin every time? (sorry if a bit off-topic)

  • reply
  • like

@sdubois I haven't planned that far ahead yet haha, no clue!

like-fill
1
  • reply
  • like

February 13, 2018 · 3:27pm

Hey there, just read the article, I think I'm missing something. What is `cache` on the line `cache.set(key, Buffer.concat(bufferedChunks))` ?

  • reply
  • like

@guillaumecisco whichever caching mechanism you use!

  • reply
  • like

In our case that's redis, but it depends on where you want to cache. That code is just illustratory ☺️

  • reply
  • like

Ok I see, I'm right now trying to implement it with node_redis :)

So I assume I will need to implement the cache.exists and cache.get part.

Thank you for this precision

  • reply
  • like

February 20, 2018 · 3:49pm

i had to use res.write() instead of res.send() in the end callback. And it seems stream.pipe() takes a boolean for 'end' parameter, not a string.

  • reply
  • like
Your message here...

*bold*_italic_`code````codeblock```