Incremental Static Regeneration for Angular | by Enea Jahollari | Sep, 2023
[ad_1]
In Angular v16 we got a new hydration system. It was also hinted that experiments for partial hydration and resumability are in the works. This is great news for Angular developers, but there is still a lot of work to be done to make Angular apps more performant and SEO friendly.
While Hydration and Resumability are great for improving the performance of our apps, they are not enough. Having to server-side render the application for each user request is not efficient and can be expensive for large applications with a lot of users.
Solutions like SSG (Static Site Generation) render the website at build time and cache the files for each app route. SSG has its own problems. For example, if a page is updated the entire site needs to be rebuilt. This can take a long time for large sites (if build caching is not applied).
That’s why we need a solution that combines the best of both worlds: server-side rendering the application for each user request and caching the pages while updating them when needed without having to rebuild the entire site.
And that’s where Incremental Static Regeneration (ISR) comes in. ISR is a technique that enables updating static pages of a site without having to rebuild the entire site.
The idea behind ISR is to server-side render the pages of our site at runtime, and then cache them for future requests. Instead of caching the pages forever, we can set a time limit for each page or route. When the time limit expires, we send the stale content to the user, and trigger a regeneration under the hood that will re-render the page and cache it again. We can update the pages of our site without having to rebuild the entire site.
ISR can update the pages of a site when the data changes. Consider a blog. When a post is updated we can re-render the page representing the post and cache it again. This is also useful for e-commerce sites. Individual product pages can be updated when price changes happen or a product is out of stock. This is also called In-Demand Regeneration because the pages are regenerated only when they have changed.
Before you can enable ISR in your application, the following steps need to be completed:
- Enable Server-side rendering in your application.
- (Optional) Include a custom cache handler (Filesystem, Firebase Firestore, Redis, Cloudflare Workers KV, etc.)
We will use @rx-angular/isr (maintained by Push-based.io which offers also other services and tools to improve web performance and scalability) to add ISR in Angular. This package provides an API that allows you to add ISR with just a few lines of code.
First, install the library:
npm install @rx-angular/isr
Next, add the ISR providers.
In a standalone app, we need to add the providers in the app.config.server.ts file:
+import { provideISR } from '@rx-angular/isr/server';const serverConfig: ApplicationConfig = {
providers: [
provideServerRendering(),
+ provideISR() // 👈 Register ISR providers
],
};
export const config = mergeApplicationConfig(appConfig, serverConfig);
In a NgModule based app, we need to add the providers in the AppServerModule:
+import { provideISR } from '@rx-angular/isr/server';@NgModule({
imports: [
AppModule,
ServerModule,
],
providers: [
+ provideISR() // 👈 Register ISR providers
],
bootstrap: [AppComponent],
})
export class AppServerModule {}
These providers will allow us to modify the rendered HTML before it is sent to the user, and also to register the routes that we want to cache, and the time limit for each route. Link to source code.
Update the server.ts file to intercept the requests and send the cached pages if they exist.
+import { ISRHandler } from '@rx-angular/isr/server';export function app(): express.Express {
// Other Angular Universal setup code (removed for brevity)...
+ const isr = new ISRHandler({
+ indexHtml, // 👈 The index.html file
+ invalidateSecretToken: process.env['INVALIDATE_TOKEN'] || 'TOKEN', // 👈 The secret token used to invalidate the cache
+ enableLogging: !environment.production, // 👈 Enable logging in dev mode
+ });
+ server.get('*',
+ async (req, res, next) => await isr.serveFromCache(req, res, next),
+ async (req, res, next) => await isr.render(req, res, next)
+ );
// remove Angular render handler as we will use the one from isr
+ (req, res) => {
+ res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
+ }
return server;
}
Now you have added ISR to your Angular app.
To use ISR, you need to add the revalidate property to the routes that we need to cache. This property accepts a number that represents the time limit for each route in seconds. For example, if you want to cache the home page for 10 seconds, you can do it this way:
const routes: Routes = [
{
path: '',
component: HomeComponent,
+ data: { revalidate: 10 }
}
];
That’s it! All the building blocks are in place, and now you can use ISR in your Angular app.
NOTE: By default the library will use the In-Memory cache handler.
- On-Demand Regeneration (regenerate the pages when the data changes, for example when a blog post is updated)
- Built-in cache handlers (In-memory and Filesystem cache)
- Plugin based cache handler (Build your own cache handler, for example Redis, Cloudflare Workers KV, Firebase Firestore, etc.)
- Error handling (If the page fails to render, we can send the stale content to the user, until the page is regenerated successfully)
- Logging (We can enable logging in dev mode to see what’s happening under the hood)
- Cache store/retrieve hooks (Update the cache before the page is stored in the cache, or before the page is retrieved from the cache to be sent to the user)
- Combining Filesystem cache with Pre-rendering (We can use ISR with pre-rendering to pre-render the pages of our site, and then cache them for future requests)
- Improved TTFB metric (Time to first byte)
- Less server resource usage because of caching
- Avoiding doing the same work twice
- Extendable APIs
- A better developer experience (DX)
- The library is open source (MIT)
When using this package there are some considerations. Pages that are dependent on user specific data can be tricky to cache and re-use for all the users. In practice it may be better to not cache those pages.
Use ISR to improve your application experience.
ISR not only improves your application performance but also improves the experience for your users. Get started with ISR today by checking it out at https://www.rx-angular.io/docs/isr.
[ad_2]
Source link