/post/4 Back

Huge increase on same-origin page loading performance.

Posted 2026-09-04 10:58:57
Hello!

You may have noticed that this page and any other same-origin pages now load instantly rather then taking upwards of 100ms to load (depending on your network speed and hardware this may vary). This is because today, I worked out a pretty decent page-preloading system, fully in javascript. This system is rather basic but uses some cool techniques to achieve the speeds yourve certainly noticed.

It takes time to load any website, regardless of size, you can usually expect atleast ~50ms of network latencey. Even on pages like this one, that are very barebones and only transfer a few KBs of data.. Another thing with same-origin page loading is that usually even if the stylesheets or other static files are cached, same-origin pages will still query the server for them, this adds to the page loading time and without a cache this becomes very apparent.

Now, the way I solved this minor botherence was by trying to find a way to preload pages BEFORE the user opens them. I've looked into a few html/lighthouse properties but they do not seem to work as well as some web developers think.. I eventually settled on the idea of just creating my own preloader.

After a few hours I've come up with the following system, this was just the most logical way I could think of, I'm sure that this idea is not original but maintain that this code was without systematic inspiration..

Step 1

First things first, we only want to preload same-origin pages, my site has both cross-origin and same-origin anchors. To segregate these types I've simply allocated a specific class preload-a which is assumed to be a same-origin link.

Using a .querySelectorAll(".preload-a").forEach we can locate and iterate through all of these asserted preload anchors. On each iteration, on every element, the code adds two event listeners one for mouseover and one for click.

Hover event

(mouseover)
When a mouseoever occurs on a preload-a, we simply start an asyncronous function in the background that is set to fetch the contents of the link that the anchor is linking too. The fetched content is then parsed using a new DOMParser which allows the code to load a second DOM alongside the main DOM. This was a useful function because I was previously trying to append the pages raw contents (html tag and all) straight onto the body as a child, the DOM cannot have two HTML tags at once and so it broke.

After parsing, the pages head and body are moved onto locally-scoped variables of their respective names.

Click event

On click, the code has to load (a click cannot happen without a mouseover meaning that the pages contents will already be fetched. This is a gauruntee) the pre-fetched head and body to the page. This action must be done with caution as if it is not done correctly the illusion breaks (More on this later). My first approach to this system was to simply replace the body and head contents with the preloaded body and head contents. This worked fine but in every method I tried for replacing the old head with the new one -- resulted in the page flashing. This was due to the stylesheet being momentarily removed from the page whilst the two elements are being swapped.

The final working system I came up with was to create an array of the old and new head elements, then append all of the new head elements to the regular document.head element, then it would iterate through each tag and remove any tags from the old head that where not in the new head. This means that if both pages shared static resources (such as stylesheets) then the old stylesheet would be used which does not cause any drop in styles because we are simply leaving already included tags inside the head.

Now that the head has been loaded, we can very easily just replace the old body wit hthe new one, this does not require any fancey inclusion mapping and can be done with a simple old_body.replaceWith(new_body). The final steps here are two firstly; disallow the anchor from its default behavior of changing the location.href value to that of its own .href as this would redirect the page. Just like any other event its easy to disallow its default behavior by taking in the event and simply doing event.preventDefault().

Now the final step is just to initialise the page. All pages share the same public script, some pages (like the posts context page) have custom javascript. Now because of this recycling system in the head, the public javascript file is not re-executed when a new page is loaded, this would be silly anyway. Instead I've moved all of the code that should run on every page -- into a function called init_page(). For ease of use I've made this function take in one optional parameter, that being the root element to use, this defaults to document. The reason for this is because back in the mouseover event stage, the parsed page is actually run through the init_page() function as this would pre-execute the initialisation of the page before it even gets put on the real DOM.

After this function is run we can use window.history.replaceState() to change the URL of the page Without actually reloading the page. This is the final step. After this has completed, the entire pages contents including the URL and title will be that of the new page, hence much quicker [visual]loading times.

Conclusion

I've added a basic performance.now() counter to see how long it takes from anchor-click to page-display and it now takes on average 0.000 ms to display the pre-loaded pages! For comparison, both calls before and after head and body swap result in the same time output..

I understand that this may still be unoptimised, I'm certain there is a more performant way of doing this, but for now, I am happy..

If you want the source code for everything im talking about you can find it here.

I understand that this may still be unoptimised, I'm certain there is a more performant way of doing this, but for now, I am happy..

If you want the source code for everything im talking about you can find it Here (Note that my implementation is only meant for html content, this is why pages such as the former do not use it). Thanks for reading