Giter Club home page Giter Club logo

ngx-isr's Introduction

⚠️ The library moved to @rx-angular/isr

Migrate to the new library by using:

The migration will:

  • Install @rx-angular/isr library latest version
  • Migrate all the changed class names imports and usages
  • Remove ngx-isr library from package.json

Automatically!

Incremental Static Regeneration for Angular

A library that enables Angular Universal applications to generate static pages at runtime and then update them incrementally on demand or on a schedule.

📰 Documentation

📰 ISR Blog post

Features

  • ⏰ Scheduled cache invalidation
  • ▶️ On-demand cache invalidation
  • 🔌 Plugin based cache handlers
  • 👌 No build changes required!
  • 🅰️ Supports Angular Universal
  • 🛡️ NgModules & Standalone Compatible

How to use it?

  1. Install npm package
npm install ngx-isr
# or
yarn add ngx-isr
# or
pnpm add ngx-isr
  1. Initialize ISRHandler inside server.ts
const isr = new ISRHandler({
  indexHtml,
  invalidateSecretToken: 'MY_TOKEN', // replace with env secret key ex. process.env.REVALIDATE_SECRET_TOKEN
  enableLogging: !environment.production
});
  1. Add invalidation url handler
server.use(express.json());
server.post("/api/invalidate", async (req, res) => await isr.invalidate(req, res));
  1. Replace Angular default server side rendering with ISR rendering

Replace

server.get('*',
  (req, res) => {
    res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
  }
);

with

server.get('*',
  // Serve page if it exists in cache
  async (req, res, next) => await isr.serveFromCache(req, res, next),
  // Server side render the page and add to cache if needed
  async (req, res, next) => await isr.render(req, res, next),
);

You can also pass providers to each of the ISRHandler methods.

server.get('*',
  ...
    async (req, res, next) => await isr.render(req, res, next, {
      providers: [
        { provide: APP_BASE_HREF, useValue: req.baseUrl }, // <-- Needs to be provided when passing providers
        { provide: CUSTOM_TOKEN, useValue: 'Hello from ISR' },
        CustomService
      ]
    }),
);

It is also possible to pass a modifyCachedHtml or modifyGeneratedHtml callbacks to the ISRHandler methods. These methods provide a way to modify the html served from cache or the html that is generated on the fly.

Important: Use these methods with caution as the logic written can increase the processing time.

server.get('*',
  // Serve page if it exists in cache
  async (req, res, next) => await isr.serveFromCache(req, res, next, {
    modifyCachedHtml: (req, cachedHtml) => {
        return `${cachedHtml}<!-- Hello, I'm a modification to the original cache! -->`;
    }
  }),
  // Server side render the page and add to cache if needed
  async (req, res, next) => await isr.render(req, res, next, {
    modifyGeneratedHtml: (req, html) => {
      return `${html}<!-- Hello, I'm modifying the generatedHtml before caching it! -->`
    }
  }),
);

ISRHandler provides APP_BASE_HREF by default. And if you want pass providers into the methods of ISRHandler, you will also have to provide APP_BASE_HREF token.

  1. Add revalidate key in route data

Example:

{
  path: "example",
  component: ExampleComponent,
  data: { revalidate: 5 },
}

NOTE: Routes that don't have revalidate key in data won't be handled by ISR. They will fallback to Angular default server side rendering pipeline.

  1. Register providers To register the ngx-isr providers, you can either import NgxIsrModule in your AppServerModule or provide provideISR in your AppServerModule providers.

Or, if you are in a standalone app, you can register the providers in your app.config.server.ts file.

  • Register using NgxIsrModule
import { NgxIsrModule } from 'ngx-isr/server'; // <-- Import module from library

@NgModule({
  imports: [
    ...
    NgxIsrModule.forRoot()  // <-- Use it in module imports
  ]
})
export class AppServerModule {}
  • Register using the provideISR function
import { provideISR } from 'ngx-isr/server';

@NgModule({
  providers: [
    provideISR() // <-- Use it in module providers
  ]
})
export class AppServerModule {}
  • Register using the provideISR function in standalone app
import { provideISR } from 'ngx-isr/server';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering(),
    provideISR() // <-- Use it in config providers
  ],
};

When registering the providers, NgxIsrService will be initialized and will start to listen to route changes, only on the server side, so the browser bundle won't contain any extra code.

Changelog

Version 0.5.5

Features

  • feat: allow NgxIsrService to be used in application code without bringing the whole library in the browser bundle
  • feat: separate the library into secondary entry points for server and browser

BREAKING CHANGES:

Imports now should be done from ngx-isr/server and ngx-isr/browser instead of ngx-isr;

// Before
import { NgxIsrModule } from 'ngx-isr';

// After
import { NgxIsrModule } from 'ngx-isr/server';

// Before
import { provideISR } from 'ngx-isr';

// After
import { provideISR } from 'ngx-isr/server';

Things exported from ngx-isr/server:

  • NgxIsrModule
  • provideISR
  • ISRHandler
  • FileSystemCacheHandler and FileSystemCacheOptions

Things exported from ngx-isr/browser:

  • NgxIsrService

Things exported from ngx-isr/models:

  • CacheHandler
  • CacheISRConfig (renamed from ISROptions)
  • CacheData
  • INgxIsrService and NgxIsrState
  • ISRHandlerConfig
  • InvalidateConfig
  • RenderConfig
  • ServeFromCacheConfig
  • RouteISRConfig

Version 0.5.4

Features

  • feat: refactor FileSystem cache handler from scratch (fixes: #35)
  • fix: buildId can be null but also undefined, added a check for it

Version 0.5.3

Features

  • feat: Introduce RouteISRConfig interface for better type safety in route data

    How to use it?

    const routes: Rotues = [{
      path: 'home',
      component: HomeComponent,
      data: { revalidate: 0 } as RouteISRConfig // 👈 Add type to route data
    }];
  • feat: Added build id support

    What is it and why do we need it?

    The build id is a unique identifier that is generated for each build. It is used to invalidate the cache when a new build is deployed. So, when a new build is deployed, every page that will be requested will be server-rendered again and not served from the cache. This way, the users will always get the latest version of the application.

    Useful when you have an external cache handler like Redis.

    How to use it?

    To use it, you need to pass the build id to the ISRHandler constructor. Angular itself doesn't generate a build id. But we can generate it using the environment file. What we can do is to set field in the environment file called buildId and set it to: new Date().getTime(),.

    Ex. environment.ts:

    export const environment = {
      production: false,
      buildId: new Date().getTime() + '', // We need to convert it to string because the buildId is a string
    };

    This way we will have a unique build id for each build because the buildId will evaluated at build time. Then, we pass the build id to the ISRHandler constructor.

    Ex. server.ts:

    import { environment } from './src/environments/environment';
    
    const isr = new ISRHandler({
      .. other options
      buildId: environment.buildTimestamp // Pass the build id
    });
  • fix: Fixed a bug where the cache was not invalidated when the build id changed

Breaking changes:

  • ISROptions is being deprecated. Use CacheISRConfig instead.

Version 0.5.2

  • feat: Migrate repository to nx workspace
  • feat: Added provideISR provider function
  • chore: Update example RedisCacheHandler to use a prefix

License

MIT

ngx-isr's People

Contributors

alethyst avatar biophoton avatar borjapazr avatar eneajaho avatar michaelbromley avatar renatoaraujoc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ngx-isr's Issues

Dynamic URL Caching

How to Cache dynamic Content using ngc-irs?

I have used like this

const routes: Routes = [
  { path: ':slug', component: PsingleComponent, data: { revalidate: 60 } }
];

How ever its not cached all the content in html page, it just cached the Skeleton.

Add documentation

  • Why do we need ISR?
  • Use-cases
  • How much does it improve our app? Example with stats?

Error when I run a build

Hi guys, how's it going?

So, when I'm building my production site, it's showing the following error:

./node_modules/ngx-isr/fesm2015/ngx-isr.mjs:666:37-47 - Error: export 'catchError' (imported as 'catchError') was not found in 'rxjs' (possible exports: ArgumentOutOfRangeError, AsyncSubject, BehaviorSubject, ConnectableObservable, EMPTY, EmptyError, GroupedObservable, NEVER, Notification, NotificationKind, ObjectUnsubscribedError, Observable, ReplaySubject, Scheduler, Subject, Subscriber, Subscription, TimeoutError, UnsubscriptionError, VirtualAction, VirtualTimeScheduler, animationFrame, animationFrameScheduler, asap, asapScheduler, async, asyncScheduler, bindCallback, bindNodeCallback, combineLatest, concat, config, defer, empty, forkJoin, from, fromEvent, fromEventPattern, generate, identity, iif, interval, isObservable, merge, never, noop, observable, of, onErrorResumeNext, pairs, partition, pipe, queue, queueScheduler, race, range, scheduled, throwError, timer, using, zip

As far as I could see, I follow steps correctly. Nevertheless, this error's happening.

I appreciate your attention.

Prevent cache page when http requests failed

It is nice to have if there is an option for caching strategy when caching a page that contains HTTP requests and for some reason (web server is unreachable) one request failed page don't cache and user serve with old cache version in this way user never seen empty page or some loading state on page when original server is down

`config.cache instanceof CacheHandler` always `false` for `FileSystemCacheHandler`

I tried to implement the FileSystemCacheHandler and saw that ISR is always using InMemoryCache. (validated via console log Using in memory cache handler!).

if (config.cache && config.cache instanceof CacheHandler) {

When checking the instance name of FileSystemCacheHandler its not an instance of CacheHandler but FileSytemCacheHandler itself:

const fsCacheHandler = new FileSystemCacheHandler({
    cacheFolderPath: join(distFolder, '/cache'),
  });
  console.log(fsCacheHandler.constructor.name)

Do i have to add any other config to get this to work? I use the handler like this:

  const isr = new ISRHandler({
    indexHtml,
    cache: fsCacheHandler,
    invalidateSecretToken: 'REVALIDATE_SECRET_TOKEN', 
    enableLogging: !environment.production,
  });

Cache with many urls

Hello @eneajaho!

Then, I've got to implement your lib in my project. It worked so good. I've implemented it in two projects. However, one of this, I'm getting a problem. Let me explain my context.
We have a site, which, many users access it by different urls. Our company has developed a site which can be hired by different customers. The layout, informations, site's configures, among others, are showed depending on the URL accessed. Example:

https://urlone.com
https://urltwo.com

That is, our customers have theirs domains. Their domains access our angular project. And, in our API, we've done a filter to identify which URL is being accessed. From this, we return the customer's site.

Then, the problem is: the cache from your lib is working as expected. However, due to cache, sometimes, in a fast moment, it gets the cache of the last site accssed. Example:

I requested: https://urlone.com. Ok, worked so good with cache. After it, I requested: https://urltwo.com.
In a fast moment, on the first load, it showed some images of the first site (urlone). I understand the why: the lib makes cache of the last page accssed (I've configured the data for 5 seconds). But, it was a problem for this context. Is it possible I make cache for differents URLs?

I hope I was clear.

Anyway, I liked your lib. As I said before: I've implemented it in two projects. For the first one, which it has just one URL, it got very, but very good. Congrats for this project.

Create Docs site

  • Using Tailwind
  • Angular Universal
  • Add blog page (for changelog)

Use ngx-isr in the docs as it's the perfect use-case for it.

Prevent cache on 404 route

I have a route to handle 404s that looks like this:

@Component({
    selector: 'kb-route-not-found',
    templateUrl: './route-not-found.component.html',
    styleUrls: ['./route-not-found.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush,
})
export class RouteNotFoundComponent implements OnInit {
    constructor(
        @Inject(PLATFORM_ID) private platformId: any,
        @Optional() @Inject(REQUEST) private request: Request,
    ) {}

    ngOnInit() {
        if (isPlatformServer(this.platformId)) {
            if (this.request.res) {
                this.request.res.status(404);
            }
        }
    }
}

which is used as a catchall:

    {
        path: '**',
        component: RouteNotFoundComponent,
        canActivate: [RedirectGuard],
    },

When this route is triggered, I do not want to cache the result. Right now my issue is that my website gets spammed by attackers trying all sorts of urls with various injection strings - fine. But this lib is caching all of those and my redis is quickly filling up!

I have tried inspecting res.statusCode in the server.ts file, but it is always 200 because the server.get('*',...) handler runs prior to the Angular route.

Is there currently a way to prevent caching when hitting a 404 route?

Add Angular 14 support

👏 First of all, this library is spectacular. Thank you for this great work! 🙏

✍️ Describe the bug
When trying to install this library in a project with Angular 14 the installation fails.

❌ Error

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! While resolving: [email protected]
npm ERR! Found: @angular/[email protected]
npm ERR! node_modules/@angular/common
npm ERR!   @angular/common@"^14.0.1" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer @angular/common@"^13.3.0" from [email protected]
npm ERR! node_modules/ngx-isr
npm ERR!   ngx-isr@"*" from the root project
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force, or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR! 
npm ERR! See /Users/borja/.npm/eresolve-report.txt for a full report.

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/borja/.npm/_logs/2022-06-13T21_46_14_526Z-debug-0.log

✅ Expected behavior
The library must be compatible with Angular 14.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.