Learning the hard way that the modern answer to "holy cow, why is the TTFB so bad and the HTML payload so bloated?" is SSR + inline'd Redux.
`window.__INITIAL_STATE__` is my nemesis.
Seriously tho; not the first time *this month* I've seen > 300K of inline'd `window.__INITIAL_STATE__` for a page whose contents isn't (and won't ever be) that large.
THIS IS WHY WE INVENTED AJAX, YO.
Yep, seen this emerging, too. How about a @____lighthouse audit for excessive embedded scripts? Same goes for styles (critical CSS sometimes gets out of hand). Also some mistakenly embedded laaarge SVGs.
a) how else would I have noticed? (I’m not auditing pages at random - yet)
b) guess who fixed it last time? (and pushing for a fix in the new version, too)
I do! Or I did. I don't really do web development anymore.
I wonder how many us were driven away by the giant do-everything-for-you frameworks/toolchains.
Still there, struggling. :)
In my case it not Redux+SSR, but Apollo+SSR and JS Styles + SSR.
But I think the main problem not with that libs, but React architecture, it requires rendering to discover components. So you aleays have multiple rendering cycles in the request path.
I'm in the middle being driven away right now. That's a slight exaggeration: I'm still at it, but I definitely do a lot more frowning, sighing, and thinking about what else I could do for a career.
Yeah — HTML all wrapped in React instead of the other way around, CSS-in-JS, Redux for trivial stuff… all killed Web dev for me; I’m now doing something else entirely.
Wake me up when the Web gets over its “let’s make all the same mistakes Java made!” phase.
I'm guilty of this too (Sapper serializes preloaded data — equivalent to getInitialProps in Next.js — and includes it to avoid `preload` running on both server and client). Are you saying that it'd be better to fetch that same 300kb asyncly even if TTI suffers as a result?
Not exactly... This approach can be great in moderation; the problem is over-inclusion in the startup snapshot. Too much data, too little of it used. Delay load what isn't in the critical path.
tricky problem to solve at the tooling level I suppose. If you're streaming the HTML response, and the 300kb is inlined at the bottom of the page, does that sufficiently mitigate the problem?
It's significantly better than some of the other approaches (from a raw latency perspective), and we can do *some* parsing off-thread. But from a tools perspective, you know what's going to be needed in initial model bootstrap via component execution on the server...right?
what about loading the data in a worker, then messaging it to the main thread? or is structured cloning 300kb of data not much better than parse/evaling it?
JSON is no good if your payload includes Map, Set, Date, RegExp etc. Not sure how <link rel="preload"> helps — just that it's getting the data in parallel with the markup?
I'm not seeing those data types in these outputs, generally. They're just string hierarchies. Complex stuff is smushed.
Wonder what tools are doing which thing.
i've found JSON to be too crude for real-world situations:
// data
var things = [a, b, c];
var selected = things[1];
payload = { things, selected }
// ui — this doesn't work with JSON
<div class={thing === selected ? 'selected' : ''}>...
Sapper uses
Bottom > start.
Ideally each component has it’s own trailer of data that you can flush before waiting for more data. Assuming your system even supports incremental flushing of responses.
React-ish component systems typically don't allow for this.
One key insight is:
You need to emit at the start of the doc *what* data is going to come via streaming, so that the UI can incrementally initialize and know what will eventually stream in to avoid starting network requests.
Go to plus.google.com/+MalteUbl & view source, and search for
AF_initDataKeys // Think of this as the GraphQL queries for which answers will be streamed
and AF_initDataCallback for the individual streaming responses.
Unfortunately there is a problem with the Promise API and SSR. Since you can't ask a promise whether it is done, you don't know when to flush the TCP buffer, so you have to guess.
Right, and I'm mostly seeing this in React apps where the model bootstrap is used to re-generate a bunch of already functional DOM.
This gives me many feels.
yeah, it's wasteful. i've found it quite hard to think up general solutions to hydrating server-rendered DOM that don't involve duplicating stuff between markup and data. v interested in any examples you can share
I can definitely imagine scenarios where your UI expects to be able to access certain data synchronously, and your server wasn't aware that it needed to provide it because it happened in a client-only lifecycle hook
Yeah as a dev you need to be aware of it but I implemented it almost a year ago in one of my projects and it rarely creates problems and when it does, it's usually easy to notice and fix it manually. I'd say it's well worth it.
...but these apps also seem to be bundling all components, not just those used on the route/page, so IDK. "DX" has apparently liberated minds from the confines of mere physics.
...once your acknowledge it's an issue.
The Cult of DX preaches that in the fullness of time, the path to (en)lightenment will be baked into your existing tools without any effort or understanding on your part.
Do you find the DX of wiz in the same BallPark as other options externally today? I find it to feel quite "different". (not that it is bad just that open sourcing it is a small small piece of getting user uptake)
I feel that this bootstrap problem is a solvable thing in both react land and angular land today, the effort involved is more creating and baking the pattern into the ecosystem. Less so creating yet another framework.
Not sure about Angular. I don’t see how it can be done in React without an ugly overlay thing like Bigpipe.
I do, however, think you can make a very React-y system that would not have these problems.
One key issue is that “good DX“ largely relies on running JS components in the server.
Something that neither Google nor Facebook have been willing to do. And thus their solutions to these problems aren’t widely available.
Always boggles my mind that people use the stack for internal apps.
@slightlylate often forgets to say this:
For Enterprise Back Office Tool Dashboard Manager 2000 the DX/UX tradeoffs of many current generation frameworks are exactly right.
At Netflix we shipped some really amazing internal data visualization apps that would absolutely have been horrible had we tried to make it publicly accessible because the size was too big. But for internal use? It was awesome.
Exactly, but like most web apps are not for public use. Most usage is public, not not most apps.
Using the same tools for vastly different use cases with different tradeoffs inevitably leads to bad outcomes for some of the use cases.
We are close to launching a Google product using Node.JS for SSR on Angular - Update in Node.JS summit next week. We are also reusing ideas from Wiz including jsaction and progressive bootstrapping. Will be closer to Wiz with Ivy renderer where we can module split better.
I'd make some changes that should be done anyway.
The DX of Wiz depends on a tightly integrated stack, and open sourcing that would be a large-team year-long effort.
you leave my
var __INITIAL_STATE__ = {readyfns: []}
</script></head>
...
fetch(...)...then(state => readyfns.forEach(fn => fn(state)))
</script>
</body>
alone! (really I just wanted to show an example alternative here for people who don't know of any)
I badly want to see one of these JS frameworks take a "sever-side first" approach".
Provide the same conveniences people like from React/Vue/etc but make SSR the default and ship only a thin layer of JS to the client.
I mean, that's what Nuxt does, it's just one command to set up... it has better lighthouse scores out of the gate than even Vue CLI pwa, last time I checked ❤️
vue init nuxt-community/starter-template <project-name>
(You have to have Vue CLI installed globally first, but you should only run that once)
This sets up a new Nuxt project.
I've used Nuxt.js but am FAR from an expert. My understanding is you have two basic options: static site or server render and then Vue takes over. Is that correct?
Is there something in the middle? Mostly static site with minimal JS for interactivity not a complete SPA takeover?
You can also use @vuejs for interactivity only and not as a complete SPA. The router and state management are not required in your project. Static site and server render options depend on your architecture choice.
Yup, that's how we started - a bit of vue sprinkled on top of spring boot + thymeleaf. Mind you, not solution I'd recommend long term/for big project, but it certainly works with no problems.
I hate to jump in with my own thing (or do I? I dunno) but @eleven_ty is exactly this. It ships sites with zero client-side JS and let’s you add what you want on top. 11ty.io/docs/
Still one of my fav hacks. I haven't had this case personally but I'd totally use that technique if for some reason I needed a huge payload up front. But its way easier to just not need a huge payload up front. (Long term easier that is…)
Yeah, seems like there is a need for a good howto article that measures the impact and provides a good alternative.
Like those blocking scripts in twitter and Facebook widget docs back in a day, we can change the patter.