Record page hit when SPA page transitioning in modern SharePoint sites

How the SharePoint site client side logic fetches page data


This is a picture based on my reverse engineering skills and I would love to see official Microsoft documentation on how the client routing works, but there isn't at the moment. So, here is my interpretation on how the router component fetches data before page load. Again, this is NOT OFFICIAL Microsoft documentation.


SharePoint process diagram

Once that data is fetched, the router component re-renders the page section with the new data and overwrites the URL of the browser using some magic under the hood. No server page render at all.


The SPFx App Customizer does not register the page transition on init anymore


As it is mentioned in that Github discussion, the app customizers do not re-render and the onInit method of the customizer does not fire when transitioning between the pages. This is by design, to improve the page load performance and it makes perfect sense. What should I use if I want to call function after page client transition then?


Record page hit after SharePoint client side page transition occurred


As it is mentioned in that same Github discussion, I should subscribe to application navigated event from the onInit method of the app customizer.


export default class CustomizerLifecycleEventsApplicationCustomizer
extends BaseApplicationCustomizer<ICustomizerLifecycleEventsApplicationCustomizerProperties> {

@override
public onInit(): Promise<void> {

this.context.application.navigatedEvent.add(this, this.logNavigatedEvent);

return Promise.resolve();
}


public logNavigatedEvent(args: SPEventArgs): void {
console.log(`REGISTER PAGE VIEW HERE=${window.location.href}`);
}
...


Due this is NEW SPFx functionality, there is currently some more work to be done to stabilize it


I had to make in my code so that event fires only once and after the page has loaded. I mentioned the current challenges I have with the same Github discussion, but I will also mentioned them here.


Currently, three additional tweaks should be made to record page hit on page transition


Here are the 3 tweaks I had to make in my code so the navigatedEvent event fires only once and after the page has loaded:


export default class CustomizerLifecycleEventsApplicationCustomizer
extends BaseApplicationCustomizer<ICustomizerLifecycleEventsApplicationCustomizerProperties> {

@override
public onInit(): Promise<void> {

console.log(`LCEVENT:onInit=${window.location.href}`);

if (!(window as any).isNavigatedEventSubscribed) {

this.context.application.navigatedEvent.add(this, this.logNavigatedEvent);

(window as any).isNavigatedEventSubscribed = true;
}

return Promise.resolve();
}

@override
public onDispose(): Promise<void> {

console.log(`LCEVENT:onDispose=${window.location.href}`);

this.context.application.navigatedEvent.remove(this, this.logNavigatedEvent);

(window as any).isNavigatedEventSubscribed = false;
(window as any).currentPage = '';

return Promise.resolve();
}

public logNavigatedEvent(args: SPEventArgs): void {

setTimeout(() => {

if ((window as any).currentPage !== window.location.href) {

// REGISTER PAGE VIEW HERE >>>
console.log(`LCEVENT:navigatedEvent=${window.location.href}`);

(window as any).currentPage = window.location.href;
}
}, 3000);
}
}


Tweak 1, add window.isNavigatedEventSubscribed to prevent double subscribe when subscribe for `navigatedEvent` from `onInit`


OnInit of the app customizer fires two times. There seem to be two instances of my app customizer class instantiated from the internal SharePoint redux single page application router SharePoint team is building. Using the Office 365 CLI, it shows the SPFx customizer is installed only once for my site:


, but when I add console log on the onInit method of the app customizer, the onInit is being called twice and NOT from the same instance of the class. There seem to be two app customizer classes running simultaneously for some reason.


Chrom developer tools opened

The problem with that is that when I subscribe to `this.context.application.navigatedEvent.add` from `onInit`, I will subscribe twice if I do not put extra checks to prevent that.


Tweak 2, add window.currentPage flag to prevent double execution due to the `navigatedEvent` sometimes fires twice


`this.context.application.navigatedEvent.add` sometimes fires twice or four times in total if `onInit` fires twice and I've subscribed from there. Microsoft said that this is fixed and rolling out, but I am still experiencing it on my tenant, so I had to add that extra check.

Here is what happens on the GIF. The `this.context.application.navigatedEvent.add` fires once, waits for some moments and fires again. Combined with the fact that `onInit` fires twice, I get `navigatedEvent` function fired 4 times.


Tweak 3, add `setTimeout` for 1000-2000 milliseconds is likely to fix the timing issues and ensure that my code will start executing after the new page is loaded by the client router


`this.context.application.navigatedEvent.add` does not fire after navigated when page is not cached in IndexedDB. So, the description of the `navigatedEvent` says that is fires after navigated, but it is wrong in specific cases.


Visual Studio code editor

I noticed that the `navigatedEvent` fires relatively after page load only when the page is fetched from the client (IndexedDB). If the Microsoft router has to fetch the page from the server, then the `navigatedEvent` event is fired before the page transition. Adding `setTimeout` is not pretty, but at least will give me the more accurate timings.


Here is a repo with the code mentioned above in case you need the complete solution


Conclusion


SharePoint becomes more and more single page application (SPA) with offline first capabilities. This introduces different way to handle events on page transitioning for the developers. It is still not mature enough yet, but the navigatedEvent fires on every transition so it can be used to deliver the business features like capturing page hits.