React code splitting became a thing a year ago. Since then, we are finding new ways to codesplit and defer component rendering. Let’s have a pause, and try to understand – why code splitting is so important. And to do so, we have to go back to the future.
Single Page Application.
SPAs — the holy cow of today, and the main code splitting subject.
You have to “split” it since SPA consist of megabytes Java Scripts, and it will take ages to load, parse, instrument and execute. And you don’t need it all. Simultaneously.
SPA, a “Single Page Application”, is not a “single page” – it is technically a single page, for a browser only, not for you. “Single Page Applications” are actually “Multi Page Applications”. Or multi view, or multi component applications. It does not matter. Secondary “pages” does exist, but you dont need them util you use them, thus you can postpone those additional pages, and the code needed to render everything, but not the current page. You can code split. And you have to codesplit.
By deferring execution of some code blocks, you are making the first blocks available earlier. By not loading the code which you don’t have to load — you are mending your application. Letting it better do things, it should do right now, not somewhere in the future. Making it more pleasurable to use.
“Pleasurable” here is almost like
loading indicators– “by showing some spinners, instead of a blank page, you make users think that your page is fast, and be ready soon, as long as they has got some perceptual signal”.
What is the difference between SPA and Old-School sites from a customer point of view?
- By clicking on links at old-school sites you will open a new page. After opening the new page you will have to load all the data once again. Execute dozens of scripts, run something from a scratch, show new ads, banners, and trackers.
- Clicking on the links in SPA will do the same… faster, and with a fancy transition animation.
So, in my humble opinion — SPAs were born to make “next page” faster. Just to remove the meaning of the “next page”. To make everything more ajaxy, more smooth, and execute all that crap that we usually load and execute on the first page view, only once.
Idea behind SPA — making next, “the second” page view faster.
But, you know, idea behind code splitting is about making that first view faster, by making the second view slower. I am not sure, but it seems code splitting is trying to solve the problem contrary to the one SPA was born to solve.
SOMETHING IS VERY WRONG HERE!
WE ARE GOING BACK TO THE FUTURE!
Server Side Rendering?
What was before SPA? Old-plain Server Side Rendering. Simple HTML. Simple CSS. Warm Sun. Green Grass. And just a few drops of JS.
Could you please answer a few stupid questions? I’ll guide you:
- Q: After the first page load — is “the picture” customer sees the same for SSR and “SPA? Is it identical on HTML level?
- A: Yes. Both solutions are looking absolutely the same!
- Q: After the “second” page load — is “the picture” customer sees the same for SSR and “SPA? Is it identical on HTML level?
- A: Yes. Yet again they are looking absolutely the same!
Now, be ready for the main magic, for the main question
- A: A megabyte. With easy! Nowadays — maybe more.
- Q: How much HTML you have to download, to render the Page?
- A: A kilobyte. Or something about.
SSR can provide the same experience, the same result, for a customer, in cost 100 times lover, in speed 100 times faster, capable to run on any device. Proven by old-school sites, my grandma’s PC could open.
It should be clear — the best experience could be achieved with SSR rendering, and only “then” — SPA with code splitting. The difference, especially not for bleeding edge devices (and networking) could be HUGE!
Even more — SSR, without any JS sent to the client, is the best code splitting ever possible, as long all the things were left on a server, and nothing not crucial(only HTML, CSS, and images are crucial) have been sent to a client.
You can use any library you want, regardless of size, moment.js for example, and still fit to 1kb networks traffic limit. You are FREE!
Code Splitting with SSR
So. You have implemented SSR for your site, but still have to convert static, I would say dead, HTML to the dynamic React App. Static sites are dead sites. So you still have to send megabytes of JS, still want to run it, and still want to code split.
Suddenly – its not as easy, as before. Why?
Complexity of SSR friendly Code Splitting
How SSR now works? In short – you render a page on a server, send it to a client, load js needed, then
hydrate HTML and it’s alive. Bingo!
By the time you call
hydrate – all scripts have to be loaded, so you have to know all the chunks needed to render some page in some state. That’s why you have to use “code splitting libraries” — they will help you keep track on chunks used. That’s not so easy as it could sound.
Modern React code split libraries, like React-loadable, loadable-components or react-imported-component capable of doing it. They will use different approaches to collect and report chunks needed for current render call. But!, even if they work differently – the end is always the same – a single command to await all used chunks.
To actually render the page, user is already could see due to SSR, you have to load all scripts, to be able to re-render this page again. To be able render exactly the same picture you already can see.
So – page is ready, but you are waiting for scripts to be loaded. This is time-to-interactive. Worst seconds you can see anything, but could not do anything….
For most of us, for me for example, the main words here — load all scripts. If you want to dive deeper into the problem — I’ve got another article about it.
React and Code Splitting made easy
Code splitting always was a thing. From the times, when you were adding tons of different script tags, till RequireJS…
But, today, lets focus on another sentence, nobody (realy! look like nobody) gives a shit before — user is already could see the site due to SSR.
Someone actually thinks — that is bad. That even page generation could increase Time-To-First-Byte or Time-To-Interactive, as long you will “spend” more time on server. Have a look on this thread. Then — I will explain what is wrong.
What is wrong here?
- 1.5s seconds spent in
renderToString. Old-school php-maden web sites usually took no more that 300ms to full render.
- Might be that was not
renderToString, but full cream
render? Don’t use it.
- Component approach — is wrong here. Component approach creates a problem, then you have to first render everything, and only then get information about page title you provided to react-helmet.
- One simple mistake in the code.
Mistake is quite simple — you don’t have to
render before you
renderToString, you don’t have to inject rendered string into the bigger template. You may, and have to be “linear”. And you can send “top lines” of your page just as soon, as it possible, and send everything else a bit later. Let client start downloading bundle. Let client see Title before you finish render-of-everything. Like sui-components’s SSR packages does.
Anyway. Lets assume that we have server-rendered page. And the main point here it quite simple and straightforward. It’s hard to deny it —
The page is ALREADY rendered.
That’s the point! Everybody should wait for all scripts to be loaded, to be able to render the page back, but it’s already rendered. It’s already here. What if you could reuse it?
“Reuse” not as rehydrate reuse it, but reuse it for code-splitting!
Idea, I am talking about is super simple
AsyncComponent, basically, is a
div. With “async result” inside.
- For client side AsyncComponent is empty in the beginning, and got filled then “async” operation resolves. It chunk got loaded.
- For Server Side AsyncComponent is NOT empty, that’s why we have to wait for async operation competition on client side to match the result HTML.
- As long AsyncComponent is not empty, it will be not empty in HTML sent from server, and could we just keep
divused to wrap around component, and display this “memoized” value while JS is being loaded.
- Don’t display “spinner” or blank space, but render the actual content,
AsyncComponentwill have to replace exactly the same HTML, but in the future, when chink got loaded. But, while future is not here — display content we get from
- In any moment of time we are displaying the same content. No visual changes. It works as common code splitting, but you don’t have to wait for all the chunks to be loaded in front.
react-prerendered-component - 🤔Partial hydration in pre-suspence era
This is library, I am talking about. Pre-rendered component. Keeps rendered content, it had inside, when it is not “live”, and starts rendering
children only when you ready to go
live. The only tricky moment here — you are controlling
live prop. And as I said — you may “go live” only then user
points on content. That’s up to you to decide.
This is not quite compatible with random code splitting library, and long they usually are not exposing loading
promises, and the only way to get it — call to static
preload, you probably should not call before some component “mounts”. But small, 10 lines long stateful component should do the job.
How it actually does this magic?
The main blocker for this pattern was the pattern itself. In React you could not access your DOM node, and read information from it, unless you render it. And if you would render it — you would wipe all the information existed in that node before.
PS: in this case “that” information “exists” only for React 16
hydratecall, and only on the first render.
To make it possible I’ve used “location IDs” provided by react-uid library, which helps me create
divs with well-known IDs, I could
querySelector in component constructor, and prefetch innerHTML before React will take control over it. And wipe it eventually.
More details, as usual, in another article. It’s not so easy as it could sounds.
Unique IDs and where to find them
The first thing, you will hear in the HTML Developers classroom –
medium.comConclusion for SSR Code Splitting
SSR Code Splitting Conclusion
You still have to use SSR friendly code splitting library, as long it’s better to be able to load used chunks in parallel. That’s becoming a not crucial, absolutely optional thing, and most of application, without deeply nested code splitting could stop using any magic tools for keeping track of used js.
But not CSS. Don’t forget about CSS.
Preloaded-component gives you more time to load stuff, including scripts and data, before actually switching view.
But it actually not the all it does.
React double definition problem
Let’s recall the original problem — you have to load JS to redraw already drawed information. I called it — React double definition problem.
You have to redefine already defined things. Render rendered. Draw Drawed.
But React Double Definition problem is… two problems. It’s Double Double Problem.
You have to load all the data you need to render already rendered information.
To re-render the page you have to load all the js being used, and load all the data being used. JS could be 1000 times “more”, than HTML its going to render. DATA… is not any better. I saw examples where hydrated normalised redux store was 100 times bigger than information rendered “from it”.
I would say — SSR just suck, as long you have to redo, reload, rerun literally anything. I am not sure SSR is a good thing. Kill it with fire, please.
ANYTHING, you made on server —is useless! Just trash it.
This is very common today, die to the way SPA fetches and consumes information, but was anti-pattern in jQuery Era.
jQuery doesn’t call to REST or GraphQL endpoints. jQuery just read all the things from data attributes you left in HTML. jQuery just traverse DOM and restore data from HTML. jQuery is so jQuery. I couldn’t say It did all the things right, but somethings were… they were useful, and I am still happy using them. Every time I have to refactor 10 years-old-site – it becomes bigger(FATter) and slower due to all “architecture” we “have” to use today.
That wasn’t a thing 10 years ago. You fathers were wiser than we are.
Probably — if you used some data(state) to render some data(HTML) — you are able to restore source data(state) from the derived view (HTML). Or, at least, partially restore it .
You may use your own rendered page as a source for your state. Just store some invisible, but important data pieces in invisible attributes or tags.
That is actually an edge case, but I was interested in “hydrating” not only HTML, but the State. It’s relatively easy to hydrate and send from Server to a Client Redux store(thats built-in feature), but close to impossible to send “internal component state”, as long there is no place you can store it. There was no place.
- In the first example we are using jQuery like way, and just scanning information back to state.
- The second example is a bit jQuery-ish, and actually stores JSON next to element.
Both capable to store “internal component state” on server side render, and restore it on client. Something you were unable to do before. Something you were able to do before the before. Back to the Future.
Don’t think about this like “I have to store some not quite useful information in the result HTML”. While one developers drink smoothies and play with React — another developers adds more and more microdata to their pages, to make their pages more discoverable, accessible, and understandable. For humans and robots.
If you able to rehydrate information from the source html — so, maybe GoogleBot will also being able.
But this stuff is not related to Code Splitting!
Indeed. React-prerendered-component is not about code splitting, but about Code Splitting Plus React16 hydrate. Feel free to use React. Feel free to use any Code Splitting library or component by your choice. Now — absolutely free and absolutely any.
React-prerendered-component is a “Plus” between AsyncComponent and Hydrate. Nothing more, nothing less.
And that’s not the end. This is just an experiment, with no production usage. A proposal. Attempt to bring some good stuff from Stone Age Era to the React World. Rethink the way we code split. Why we code split and why.
And, it sounds like – this is only approach which could work well with upcoming React Suspense Streamed Render, as long don’t have to “wait”. Let’s see. In the future.
PS: If I did not convince you
There is another article, almost absolute the same problem, but without a solution. Netflix just kept React on ServerSide, and called it a day.
A Netflix Web Performance Case Study
Improving Time-To-Interactive for Netflix.com on Desktop
They had that “double definition problem”, I was talking about. 270kb of JSON in index.html. HOW IT COULD BE POSSIBLE!
Read their article. And then — reread this one.