Skip to content
This repository was archived by the owner on Feb 6, 2025. It is now read-only.
This repository was archived by the owner on Feb 6, 2025. It is now read-only.

The learnings from the app-shell and page-shell structure #1

@ritz078

Description

@ritz078

Hi,

We have experimented with app-shell and are using page-shell currently (with a slight modification - loading the whole view in advance).

App Shell
It gave us better time to first paint but the time to first meaningful paint was delayed. Here's what we found in our experiment.

cxabh40uuaamded

As a user you get a faster first paint but the time to first meaningful content is what we are looking to optimize so we tried to fix that.

Solution
We got rid of app shell and implemented SSR with HTML streaming in combination with preload. And the result was :

201 1 - how we built it - part 1 housing

This way we were able to improve the time to first meaningful paint which made more sense than just improving the time to first paint.

Page Shell
Since we had implemented SSR and the idea of page-shell is same as that of app-shell, we used the idea rather than the implementation.

Case 1: Implement page-shell -> First meaningful paint still suffers. 2 JS files to load and when you navigate from another URL, loading multiple files (we also have separate CSS to prevent FOUC) didn't seem to be a good idea.

Case 2: Implement SSR -> Let the view have 2 files (JS and CSS). When the user lands directly on the page you don't need any shell due to SSR. When you are on a different page and the next intent of the user is to come on that page, load the view of current page in the idle time of previous page.

Implementing these we were able to solve for :

  • too many files
  • better time to first meaningful paint
  • faster route navigation

All of these were more about the implementation rather than webpack bundling.

webpack was mainly optimized to implement long term caching, manifest and chunking.

Intent based chunks
We implement route based chunks but we often ignore this. This is one important implementation that can reduce the size of a view bundle. There are certain parts of a view that are not always needed like a modal or anything that doesn't show up everytime. That can be made into a different chunk and loaded on demand. For example:

201 1 - how we built it - part 1 housing

The size in the above image is non-gzipped (around 9KB gzipped).

We are using 2 types of intent based chunks which are loaded in idle time or as per need.

Type 1 :
Those which have a chance of being visible but not immediately. They are loaded in idle time like some frequently visited modals. (driven by analytics)

Type 2:
Those which have less chance of being visible or once in many sessions. Load when actually required. Most of the times its not needed. (as per our analytics data)

We have to take care of bandwidth consumption of the user. Loading lot of chunks in idle time also may not be a good idea for people with slow internet connection.

I hope these are useful for developers who are trying to make a PWA.
So in both cases out time to first paint was around 2.2-2.3s so it was better that we implement the 2nd solution.

Its not always necessary that these will work for you so experiment and then implement.

Edit : Added clarity about intent based chunks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions