My goal from last time: reuse our existing APIs in a demo of the fastest possible version of our ecommerce website… and keep it under 20 kilobytes....
For further actions, you may consider blocking this person and/or reporting abuse
Years ago, I remember using a similar technique to stream JS that would update a progress bar for a long running server process. The PHP process would keep sending JS (comment lines I believe) to keep the connection open, and would occasionally drop in an
updateProgress()
call. The browser was happy to run the JS as it came in. I was amazed it worked, but work it did... Streaming JS!Do you remember if it was called COMET, or related to that?
I wrote it all myself. Didn't name it
One of the maintainers of Marko has written his own reactive framework in the meantime called Solid.js, which also adopted streaming capabilities.
It is slightly more react-like than Marko, so your other developers might feel more at home with it. Maybe you want to check it out.
Yeah, Ryan and I chat frequently in the Marko Discord. If Solid had adopted those streaming capabilities back when I embarked on this demo, it probably would have been a compelling option.
I just noticed today that safari is not working very well with streamed HTML. It waits for the whole document to download before it wants to render anything.
Maybe that is because I have a streaming custom element in the page but that doesn't make sense at all if Chrome is happy with what I do.
I suspect that’s because Safari doesn’t support Declarative Shadow DOM yet — does the page stream fine without the custom element?
I tried adjusting things and find that the document itself is supported very well.
The real issue is, (on safari), If I modified style or class of an element, when it is still streaming, the style change won't happen until the element is complete. I am not sure it is just style and class, or it is I just cannot set and attributes.
Yeah, it’s kind of a long story. The gist of it though is I suspect you’re right, the streaming custom element sounds like the culprit due to historical plumbing issues. Not sure how you can work around it until Safari updates their support.
I do that to avoid flashing of unregistered custom element (the custom element version of fouc). And my workaround is, allow that in safari.
The interesting part is that, I cannot even do :not(:defined){visibility: hidden} or :not(:defined){display:none}. What I can do, with safari, is,
Do
opacity
orfilter: opacity(…)
work?Yes that works. But really no much difference I guess.
But it is not a DSD, it is a normal custom element driven by javascript
I think this is an aspect that assisted SPA adoption in general. Given that at the time MPAs were relatively slow to respond (server frameworks probably played a part as well), a ready-to-go (smallish) package of JS could have been shipped to the client and started doing some useful work.
Now when discussing web performance SPA proponents often counter that tuning with respect to FCP and TTI only effects initial page load — missing the point that if page loads are universally fast you may not need that SPA - unless you're going offline-first.
Back in 2013 Ilya Grigorik talked about "Breaking the 1000ms Time to Glass Mobile Barrier" and here we are 9 years later where multi-second load times on mobile are not unusual.
While now dated (2013: during 4G adoption) he describes the complexity of sending a packet over the cellular network which increases the latency for mobile compared to regular networks (sometimes I'm surprised anything works).
He also points out that when it comes to web page load times reducing latency matters more than increasing bandwidth (More bandwidth doesn't matter (much)).
Patrick Steele-Idem (2014): Marko versus Dust
"Marko was developed to address some of the limitations of Dust as well as add a way to associate client-side code with specific templates to rein in jQuery, provide some much-needed structure, and provide reusable widgets. Version 1.0 was released in May 2014." from eBay's UI Framework Marko Adds Optimized Reactivity Model
Basically if you didn't geek out over HTTP servers and the HTTP spec you probably didn't know or think about "chunked transfer-encoding" (in connection with HTML responses). And since about 2016 online searches would funnel you to the Streams API.
The rest of this series will essentially be illustrating “are you sure you need that SPA?”
Offline-first is usually the purview of SPAs, or at best an app-shell model bolted onto a content-heavy website. However, I was able to do some mad science with Marko… its compiled-for-server representation only has to write to an outgoing stream. You probably see where this is going. (More on that later.)
Ilya’s work definitely inspired me. Subsequent MPA interactions luckily don’t have to do the full mobile connection TCP warmup if you use
Connection: keepalive
, and if you have analytics pinging your origin every 30 seconds anyway, that socket rarely gets torn down. We’ll see some of my measurements around that later.Great point about the Streams API. Maybe we need an entirely new term altogether.
An network proxy could break streaming likely. if the proxy buff the response, it won't send the chunk to client/user until it receives all the chunks.
lack of CDN support is another issue I guess. Did you ever run into this kind of issue? if you did, how did you fix it?
I have yet to find a CDN that doesn’t work with streaming, but apparently AWS Lambda doesn’t. (I don’t use AWS for other reasons, so this has never been relevant for me, but it may for others.)
Even if a misbehaving middlebox buffers the response, at least it’s no worse than doing the SSR all at once like before.
what's about compression? does it work with streaming?
Yep! Each chunk of the stream can be compressed — Node.js in particular was designed to excel at combining gzip with
Transfer-Encoding: chunked
Oh, I can't wait for the next posts in this saga! :) I've criticized SPAs for their lack of performance, stability, SEO, accessibility etc for a long time now, and I can't wait for an actual story or someone finding an alternative to API-dependent (or "non-static server depending") rendering...
I think you’ll like the (currently) fourth post, then
I'm glad Kroger is working on their performance issues. I remember going there and seeing a sign that if I sign up for an e-coupon I could get a deep discount on an item and it took 10 minutes just to log in on my phone. It was an extremely frustrating experience. But since I had more shopping to do it wasn't a huge deal, but still ridiculous.
I always curse devs that don't care about performance. Especially when it is known that the customer will be on a cellular connection. They make the experience horrible for people on slow cellular connections.
And for making websites overly complex. Just keep it simple using straight up simple HTML/CSS most of time works for most sites.
This is methodical. I love it.
I couldn’t find the last part of the story:
I haven’t written it yet — sharp eye!
oh, that's great. I was a bit thrown off by the "(2 Part Series)" at the beginning and end. A "To be continued..." at the end, or an "(X Part Series)" would have helped.
Yeah, the series UI on Forem was intentionally designed to avoid showing TBA posts. I’ll edit the last paragraph
Not sure how fast comparing to your case, there’s a streaming react framework 👉 ultrajs.dev
Oh neat, this one is new to me. Do they have an example app somewhere I can point WebPageTest at?
One where it’s streaming from an API, specifically . The stuff on their
/examples
page seems to be all static so farLove the series so far. Some more data points related to streaming
Instagram wrote about streaming HTML in their engineering blog: instagram-engineering.com/making-i...
LinkedIn does something similar, but with API data responses; data is flushed and streamed into the DOM inside script tags. This helps to parallelize the typical SPA lifecycle of 1) load the JS 2) make API calls 3) render views once the data is returned.
Love these posts! Looking forward to the rest :)
«
It’s easier to show than to explain:
** content (image/gif?) misssing **
«
dev.to sometimes has that happen to
<video>
elements, not sure why. A refresh usually fixes it for me.ah thanks, that worked. iOS Safari here. Dev.to is not always the best with html tags in posts, I’ve noticed.
Good to know it’s a cross-browser bug, at least; I notice it on desktop Firefox
Logged in just to like this :)
This is my answer to streaming HTML. github.com/jon49/html-template-tag...
I've played with it in a service worker for offline use.
Very interesting read! What are your thoughts on Remix (remix.run/) as an alternative to streaming? Sorry if this is a silly question, I'm still a novice with regards to web performance.
I was asked this question on Twitter too, so you may be interested in the answer there. TL;DR: I’m not sure it would be as fast, but I haven’t tried it myself and measured yet.
However! You may not have to choose. The Remix devs’ replies on Twitter suggest that Remix will probably support streaming once React 18 releases.
Super interesting article (and series), thanks Taylor!
Great post! I like your style and approach. :D
I have to admit I wasn't too attentive, but this thing reminded me about hotwired.dev/ and all the libraries on that page. Maybe you'll find it useful for something.
Hotwire’s Turbo Streams sound similar, but they’re surprisingly different — they’re JS-powered ways to do in-page updates from a subresource. Streamed HTML is a no-JS technique that works for the very first round-trip.
Indeed. They just look similar to that
<await>
tags. And the overall "use as much good 'ol HTML as possible" attitude. :)Yeah, the attitude itself I am all in on.
Why bother with the additional overhead of JavaScript when CSS now does the job with:
<img src=“…” loading=“lazy” alt=“#”>
Demo to be tested with tools.pingdom.com/#5fed249586400000 shows 4.4MB page load in a fraction of a second!
this-is-a-test-to-see-if-it-works....
The loading attribute on the
<img>
image embed (HTML) element deals only with the deferred loading of images.The article discusses the streaming of the HTML page which typically means flushing the top part of the page early while the server is still assembling the bottom part, perhaps waiting for results from one or more service APIs.
That way the browser can process the document meta data (
<head>
) element and to start downloading any additional resources required, perhaps even offer some "top of the page search functionality" before the rest of the HTML page has even finished loading.Marko goes even further by supporting progressive rendering with async fragments.
An example from as far back as late 2014 demonstrates this approach.
Hey, apparently this is possible now with NextJS with server components (nextjs.org/docs/app/building-your-...)
Marko just looks like PHP/Laravel's Blade boilerplate.
Finally NextJs 13 implements it in React! nextjs.org/docs/advanced-features/...