Loading stylesheets asynchronously using a TagHelper in ASP.NET Core

 
 
  • Gérald Barré

In a previous post, I've talked about inlining a CSS file in the html page. In term of performance, this is important to inline the critical CSS. However, the rest of the styles of the page doesn't need to be downloaded immediately. For script elements, you can simply add the attribute async to instruct the browser to load the resource asynchronously. For stylesheets, there is no equivalent. You have to write a custom JavaScript snippet to simulate the same behavior. Of course, you must handle the case where scripts are disabled.

Here's what the html looks like to load stylesheets asynchronously:

HTML
<noscript id="render-onload">
    <link rel="stylesheet" href="/css/prism.min.css" />
</noscript>
<script>
    var renderOnLoad = function() {
        var e = document.getElementById('render-onload');
        var n = document.createElement('div');
        n.innerHTML = e.textContent;
        document.body.appendChild(n);
        e.parentElement.removeChild(e)
    };

    if(window.requestAnimationFrame) {
        window.requestAnimationFrame(function(){window.setTimeout(renderOnLoad, 0)})
    } else {
        window.addEventListener('load', renderOnLoad);
    }
</script>

The idea is to replace the element noscript by a div once the page is loaded, so the code is interpreted and the browser starts loading the assets. This way it doesn't block the initial rendering of the page.

With a TagHelper, you can replace this HTML code with a single tag:

HTML
<render-on-load>
    <link rel="stylesheet" href="~/css/prism.min.css" asp-append-version="true" />
</render-on-load>

This is far much readable and reusable across pages or projects. Tag helpers are very useful to extract reusable behaviors!

If you are not familiar with TagHelpers, you should read some of my previous posts:

The TagHelper consists of replacing the name of the tag with noscript and add the JavaScript code after the element. Compared to the previous tag helpers, here I need to get the content of the element. You can do that by using output.GetChildContentAsync(). It will process the razor content, so other tag helpers are processed to compute the content. This is why you can use asp-append-version="true" in the link element.

C#
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.TagHelpers;

[HtmlTargetElement("render-on-load")]
public class RenderOnPageLoadTagHelper : TagHelper
{
    [HtmlAttributeName("id")]
    public string Id { get; set; }

    public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
    {
        var content = await output.GetChildContentAsync();
        output.TagName = "noscript";

        var id = string.IsNullOrEmpty(Id) ? "render-onload" : Id;
        output.Attributes.Add("id", id);
        output.PostElement.AppendHtml("<script>var renderOnLoad=function(){var e=document.getElementById('" + id + "'),n=document.createElement('div');n.innerHTML=e.textContent,document.body.appendChild(n),e.parentElement.removeChild(e)},r=window.requestAnimationFrame;r?r(function(){window.setTimeout(renderOnLoad,0)}):window.addEventListener('load',renderOnLoad);</script>");
    }
}

To use the TagHelper, you need to declare it in the _ViewImports.cshtml. There are other ways to include the Tag Helper as explain in the documentation, but this is the most common.

HTML
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, MyWebApp [Replace MyWebApp with the name of the assembly that contains the TagHelper]

Here's how to use it:

HTML
<render-on-load>
    <link rel="stylesheet" href="~/css/prism.min.css" asp-append-version="true" />
</render-on-load>

<render-on-load id="images">
    <img src="~/images/banner1.png" asp-append-version="true" />
    <img src="~/images/banner2.png" asp-append-version="true" />
</render-on-load>

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub