ASP.NET Core Asset Optimization for SaaS Apps

Strategies for optimizing static assets in ASP.NET Core to enhance performance and user experience in SaaS applications.

by Carl Poppa and SEObot
ASP.NET Core Asset Optimization for SaaS Apps

ASP.NET Core Asset Optimization for SaaS Apps

Optimizing static assets in ASP.NET Core ensures faster load times, reduced server costs, and better user experience for SaaS apps. Here’s how you can achieve it:

  • Static Asset Delivery: Use caching, enable HTTPS, and compress files for better performance.
  • Caching Strategies: Implement browser caching, distributed caching with Redis, and asset versioning.
  • Minification and Bundling: Minify and bundle CSS/JS files to reduce size and HTTP requests.
  • Image Optimization: Compress images, use lazy loading, and consider a media CDN for faster delivery.

These steps can significantly enhance the speed and efficiency of your SaaS application, improving user satisfaction and lowering operational costs.

Static asset file sizes can be reduced by over 80% in .NET 9

Setting Up Static Asset Delivery

Here’s how to configure static asset delivery in ASP.NET Core for better performance and security.

Configure Static Files in Program.cs

Set up static file middleware in your Program.cs file:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.UseStaticFiles(new StaticFileOptions {
    OnPrepareResponse = ctx => {
        const int durationInSeconds = 60 * 60 * 24 * 30; // 30 days
        ctx.Context.Response.Headers[HeaderNames.CacheControl] =
            $"public,max-age={durationInSeconds}";
    }
});

Make sure to organize your static files under the wwwroot directory. Common subfolders include css/, js/, images/, and lib/.

After that, focus on improving security and performance by enabling HTTPS and compression.

Enable HTTPS and Compression

Add HTTPS redirection and file compression to your application:

app.UseHttpsRedirection();
app.UseResponseCompression();

services.AddResponseCompression(options => {
    options.EnableForHttps = true;
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();
});

This setup ensures secure connections and reduces file sizes for faster delivery.

Use a CDN for Static Assets

To further enhance performance, host your static assets on a Content Delivery Network (CDN). Update your static asset URLs to point to the CDN endpoint (e.g., https://your-cdn-endpoint.com).

app.UseStaticFiles(new StaticFileOptions {
    FileProvider = new PhysicalFileProvider(
        Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")),
    RequestPath = "/static",
    OnPrepareResponse = ctx => {
        ctx.Context.Response.Headers.Add("Access-Control-Allow-Origin", "*");
    }
});

For better caching, use versioning by appending unique hashes to asset URLs. This ensures users always get updated assets when changes are made.

Stay tuned for the next section, where we dive into advanced caching strategies to optimize your asset delivery even further.

Caching for SaaS Apps

After optimizing static asset delivery, setting up effective caching is another way to boost performance.

Browser Cache Settings

Configure cache-control headers and ETags in ASP.NET Core to manage browser caching:

services.AddResponseCaching();

app.UseResponseCaching();
app.Use(async (context, next) =>
{
    context.Response.GetTypedHeaders().CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue()
    {
        Public = true,
        MaxAge = TimeSpan.FromDays(30),
        MustRevalidate = true
    };

    await next();
});

You can adjust cache durations depending on the asset type:

Asset TypeCache DurationCache-Control Header
Images/Icons30 dayspublic, max-age=2592000
CSS/JavaScript7 dayspublic, max-age=604800
Dynamic Assets1 hourprivate, max-age=3600

For applications running on multiple servers, consider implementing distributed caching for better performance.

Multi-Server Cache Setup

Use Redis to manage distributed caching across servers:

services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "your-redis-connection-string";
    options.InstanceName = "SaasAssetCache_";
});

Set up distributed caching with custom configurations:

public class AssetCacheService
{
    private readonly IDistributedCache _cache;

    public async Task CacheAssetAsync(string key, byte[] assetData)
    {
        var options = new DistributedCacheEntryOptions()
            .SetSlidingExpiration(TimeSpan.FromHours(24))
            .SetAbsoluteExpiration(TimeSpan.FromDays(7));

        await _cache.SetAsync(key, assetData, options);
    }
}

Cache Update Methods

To manage updates to cached assets, you can use these two methods:

  1. Asset Fingerprinting: Add versioning to file paths to invalidate caches automatically:

    public static class AssetVersioning
    {
        public static string GetVersionedPath(string path)
        {
            var physicalPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", path);
            var fileHash = CalculateFileHash(physicalPath);
            return $"{path}?v={fileHash}";
        }
    }
    
  2. Cache Busting: Remove outdated cache entries programmatically:

    public class CacheBuster
    {
        private readonly IDistributedCache _cache;
    
        public async Task InvalidateAssetCacheAsync(string pattern)
        {
            var cacheKey = $"asset_version_{pattern}";
            await _cache.RemoveAsync(cacheKey);
        }
    }
    

For smoother transitions during deployments, consider preloading frequently used assets:

services.AddHostedService<CacheWarmupService>();

public class CacheWarmupService : IHostedService
{
    public async Task StartAsync(CancellationToken cancellationToken)
    {
        await PreloadCommonAssets();
    }

    private async Task PreloadCommonAssets()
    {
        var commonAssets = GetCommonAssetsList();
        foreach(var asset in commonAssets)
        {
            await _cacheService.WarmupAssetAsync(asset);
        }
    }
}

These strategies ensure your SaaS app maintains high performance while handling updates to static assets efficiently.

Discover the Best SaaS Boilerplates. Explore a curated collection of SaaS boilerplates and starter kits tailored for developers. Build scalable, feature-rich SaaS applications faster with tools designed for efficiency and success.

Asset Size Reduction

Reducing asset sizes works hand in hand with caching and delivery adjustments to improve SaaS performance. Below are methods to shrink file sizes and minimize HTTP requests.

Code File Minification

Set up minification in Startup.cs to streamline your code files:

services.AddWebOptimizer(pipeline => {
    pipeline.MinifyJsFiles();
    pipeline.MinifyCssFiles();
});

app.UseWebOptimizer();

You can also add custom rules to fine-tune the process:

services.AddWebOptimizer(pipeline => {
    pipeline.MinifyJsFiles()
        .WithConfig(new JsMinificationOptions {
            PreserveImportantComments = false,
            RemoveWhitespace = true
        });
});

After minifying, you can bundle files to further reduce HTTP requests.

File Bundling Guide

File bundling combines multiple files into one, cutting down on HTTP requests. Here’s how to bundle and minify CSS and JavaScript:

services.AddWebOptimizer(pipeline => {
    pipeline.AddBundle("/css/bundle.css",
        "css/site.css",
        "css/layout.css",
        "css/theme.css")
    .MinifyCss();

    pipeline.AddBundle("/js/bundle.js",
        "js/site.js",
        "js/validation.js",
        "js/ui.js")
    .MinifyJsFiles();
});

External Optimization Tools

For additional optimization, you can integrate third-party tools like NUglify for JavaScript and CSS Nano for stylesheets:

public void ConfigureServices(IServiceCollection services) {
    services.AddWebOptimizer(pipeline => {
        // NUglify integration
        pipeline.AddJavaScriptBundle("/js/optimized.js",
            new NUglifyJsOptions(),
            "js/**/*.js");

        // CSS Nano integration
        pipeline.AddCssBundle("/css/optimized.css",
            new CssNanoOptions(),
            "css/**/*.css");
    });
}

These tools can provide more advanced optimization options for your assets.

Image and Media Files

Reducing Image Sizes

ASP.NET Core works seamlessly with libraries like ImageSharp to handle image processing and compression during uploads. Here’s an example of how to set it up:

services.AddImageSharp(options => {
    options.Configuration.ImageFormatsManager.SetEncoder(
        JpegFormat.Instance,
        new JpegEncoder { Quality = 85 }
    );
});

For resizing images dynamically, you can use middleware to process them on the fly:

app.UseImageSharp(new ImageSharpMiddlewareOptions {
    CacheMaxAge = TimeSpan.FromDays(30),
    CacheControlHeader = "public",
    OnParseCommandsAsync = context => {
        // Automatically resize large images
        if (context.ImageDimensions.Width > 2000) {
            context.Commands.Resize(new ResizeOptions {
                Mode = ResizeMode.Max,
                Size = new Size(2000, 0)
            });
        }
        return Task.CompletedTask;
    }
});

To further improve performance, pair these server-side optimizations with smart image loading techniques on the client side.

Smart Image Loading

Modern browsers now support lazy loading with the loading attribute, which delays the loading of images not initially visible on the screen. To implement this, simply include the attribute in your image tags:

<img
  src="large-image.jpg"
  loading="lazy"
  width="800"
  height="600"
  alt="Description"
/>

You can also use ASP.NET Core Tag Helpers to create responsive srcset attributes for better handling of different screen sizes. Here’s an example:

public class ResponsiveImageTagHelper : TagHelper {
    public override void Process(TagHelperContext context, TagHelperOutput output) {
        output.Attributes.SetAttribute("srcset",
            "/images/small.jpg 300w, " +
            "/images/medium.jpg 600w, " +
            "/images/large.jpg 900w"
        );
        output.Attributes.SetAttribute("sizes",
            "(max-width: 320px) 300px, " +
            "(max-width: 640px) 600px, " +
            "900px"
        );
    }
}

These methods can dramatically improve load times and enhance the user experience for SaaS applications.

Setting Up a Media CDN

For even faster delivery of images and media files, consider using a dedicated media CDN. Here’s how you can configure Azure CDN for your media assets:

public void ConfigureServices(IServiceCollection services) {
    services.AddAzureCdnService(options => {
        options.ProfileName = "saas-media-cdn";
        options.EndpointName = "media-endpoint";
        options.OriginPath = "/media";
        options.CompressionEnabled = true;
        options.QueryStringCachingBehavior = QueryStringCachingBehavior.IgnoreQueryString;
    });
}

You can also define custom caching rules for various file types:

app.UseAzureCdnService(options => {
    options.CacheExpirationTime = new Dictionary<string, TimeSpan> {
        { ".jpg", TimeSpan.FromDays(7) },
        { ".png", TimeSpan.FromDays(7) },
        { ".svg", TimeSpan.FromDays(30) },
        { ".webp", TimeSpan.FromDays(7) }
    };
});

To ensure browsers that support WebP receive optimized images, configure your CDN with delivery rules like these:

services.Configure<CdnOptions>(options => {
    options.DeliveryRules.Add(new DeliveryRule {
        Name = "ServeWebP",
        Order = 1,
        Conditions = new List<DeliveryRuleCondition> {
            new DeliveryRuleCondition {
                Name = "RequestHeader",
                Parameters = new Dictionary<string, string> {
                    { "HeaderName", "Accept" },
                    { "Operator", "Contains" },
                    { "MatchValues", "image/webp" }
                }
            }
        },
        Actions = new List<DeliveryRuleAction> {
            new DeliveryRuleAction {
                Name = "UrlRewrite",
                Parameters = new Dictionary<string, string> {
                    { "SourcePattern", "(.*)\\.(jpg|png)$" },
                    { "Destination", "$1.webp" }
                }
            }
        }
    });
});

This setup ensures images are optimized for quality and performance, helping your SaaS application deliver a faster and smoother experience.

Summary and Action Items

Main Points Review

ASP.NET Core offers built-in tools to improve static asset performance in SaaS applications. Its capabilities are backed by TechEmpower benchmarks, making it a strong option for SaaS development.

Here are the key techniques discussed:

  • Asset Delivery Setup: Ensure proper static asset mapping, enable compression, and enforce HTTPS.
  • Caching Strategy: Set up browser caching and implement multi-server caching.
  • Size Optimization: Minify and bundle files to reduce their size.
  • Media Optimization: Use image compression and smart loading strategies.

Focus on the techniques that align with your SaaS application needs. These methods can be implemented effectively with tools designed for SaaS platforms.

SaaS Development Tools

Best SaaS Boilerplates offers pre-built ASP.NET boilerplates that include features like asset optimization, authentication, admin dashboards, and payment integrations to speed up your SaaS development process.

Some advantages include:

  • Faster Development: Pre-configured setups help you get started quickly.
  • Proven Techniques: Incorporates industry-standard optimization methods.
  • Built-in Security: Protects against XSS and CSRF attacks.
  • Authentication Support: Includes ready-to-use multi-factor authentication.

You’ll find below some of the most highly recommended ASP.NET Core SaaS boilerplates gathered from the dev community