What is application shell and how to improve the Angular application performance using Angular app shell technique


Angular App Shell

The angular app shell is a special technique to show very minimalist content until the web browser downloads the actual content of the page, So how this matter? This technique helps in two ways, firstly it improves the customer experience by letting users know that web page is loading and secondly and the most important thing it reduces First Contentful Paint time drastically and hence it improves your page performance score.

There are many questions asked around Angular App shell technique in the official Git Repo, So in order to help the community, Git repository has been added for the reference and hosted these applications in Heroku so that anyone can easily compare the results in PageSpeed Insights

Angular WITHOUT app shell

  1. Github Repo:https://github.com/allabouttech0803/universal-tutorial-without-app-shell
  2. Demo URL:https://universal-tutorial-woapp-shell.herokuapp.com/tutorials

Angular WITH app shell

  1. Github Repo:https://github.com/allabouttech0803/universal-tutorial-with-app-shell
  2. Demo URL:https://universal-tutorial-app-shell.herokuapp.com/tutorials

After performing multiple tests, indeed the application with App shell enabled wins by 9% on average compared to application without app shell and please note that performance is analysed from web.dev


Performance results of Angular Application WITHOUT App Shell


Performance results of Angular Application WITHOUT App Shell

Performance results of Angular Application WITH App Shell

Performance results of Angular Application WITH App Shell

As you can notice from the above results that the first conteful paint time is reduced by 1.2 seconds this will help in showing the users as early that the web application is loading

So now if you want to enable the app shell to your existing application follow these steps to add app shell to your existing application

Step 1): Generate the the shell component by executing below command

ng generate app-shell

Step 2): You can notice that there are some changes in your workspace after running above angular schematics, such as there is a new entry in angular.json for app-shell and also majorly there is new file introduced named with app.server.module.ts as shown below, In fact, this is where all magic happens as you can see that there is new route is introduced with 'shell' and this will be pre-rendered during build time itself and displayed to the users until real content is loaded

Note: Currently there is a bug in Angular schematics generation, in case if you are using default route with path ** in app.routing.module.ts then app shell doesn't load hence as temporary fix we have to reset the route configuration in the constructor of app.server.module.ts hence we manually added the line this.router.resetConfig(routes);

ng run projectname:app-shell --configuration=production

Step 3): Then add the necessary view, you want to display in the app shell component and here major point to be noted that you should add very minimal HTML, CSS content so that browser loads app shell very quickly. The best option for this would be to add the spinner view in your app shell component so that it’s an indication to the users that the application is still loading. you can see we have added the HTML and CSS to view the app shell as below

Angular App Shell Live

Step 4): Then the most important thing is you need to build your application using below command, here you replace projectname string with your actual project name present in the angular.json

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';

import { AppModule } from './app.module';
import { AppComponent } from './app.component';
import { Routes, RouterModule, Router } from '@angular/router';
import { AppShellComponent } from './app-shell/app-shell.component';

const routes: Routes = [{ path: 'shell', component: AppShellComponent }];

@NgModule({
  imports: [
    AppModule,
    ServerModule,
    RouterModule.forRoot(routes),
  ],
  bootstrap: [AppComponent],
  declarations: [AppShellComponent],
})
export class AppServerModule {
  constructor(private router: Router) {
    this.router.resetConfig(routes);
  }
}

References: