Lazy Loading of Fragments
Doing time consuming operations on the server before a page starts to be delivered to the browser means User Experience (UX) will suffer. It means that the user is staring at a blank page until at least the HTML and CSS has been downloaded, parsed and rendered. Download is delayed by these time consuming operations (heavy rendering, slow database queries). This makes for a bad experience and in extreme cases it can timeout on the server or the user could just give up on waiting.
Just because there are ways to mitigate this, it doesn't mean you should not fix the root cause of slowdowns. This usually means that you are doing something wrong and fixing lazy loading ad-hoc shouldn't be the only fix you do. Still, it can give you a room to breathe until you find a way to fix it permanently. Although users suffer less from this issue, the server is still getting swamped. Make sure not to forget about the root cause of the performance issues because perceived performance for users is so good after this simple optimization. Strive for faster server responses and quicker load times with user experience in mind.
Simulated slow code using a for loop and some randomization: Slow code implementation
Problem: example + source code
Plain and simple, everything is rendered in one run, slow code is included directly via include.
Long wait, big initial HTML
The proposed solution is to asynchronously load fragments of the page using AJAX. It will defer slow code execution in time and not block the main thread.
Variant 1 — lazy load: example + source code
JS code is inlined directly after div container, so the XHR request will start as soon as this element is parsed by the browser.
We've added a "spinner" to inform the user that something is going to show up there.
The file starts to download much faster and is much smaller, so it renders sooner.
Variant 2 — lazy load external: example + source code
async attribute and is placed in HTML after all the remote containers. The URL to the downloaded content is in the data attribute of its container. This way the code is easily reusable.
Same situation, snappy rendering and small file
The asynchronous nature of AJAX requests means that as soon as the given request is finished, it will insert its result to a page in a given place. This also means that if you have an error in one of your modules, only this particular piece of page will break, not the whole page.
Because you had to split the page into smaller chunks, you can now implement cache more precisely. One fragment you can expire after one hour, one after one day and one not cache at all. This is very useful when you did a split on a big GQL query that now can be reasoned on a "feature" basis.
When looking at PageSpeed reports look at the film strip - this is the most important from the user's perspective. Users need to know that something is happening as soon as possible and ideally less important things are downloaded later or never (lazy loading with scroll observer is often used for images for example).
Slow version: nothing is displayed for a long time, then everything appears
Lazy loading: renders the page, and shows loading indicators for asynchronous modules
Adding caching mechanisms to this technique might give you even better results. Try adding ServiceWorker or server-side caching.
If you load remote content with interactivity, you would need to initialize it after it's been loaded, for example using custom events.