Oğuzhan KARAGÜZEL

OĞUZHAN KARAGÜZEL

Software Developer

Website Speed and Core Web Vitals: ASP.NET Optimization

Author: Oğuzhan KARAGÜZEL ·

In the final part of the SEO series we look at website speed. Previous parts: on-page SEO, technical SEO and Open Graph, structured data and ARIA. The advice here is written with developers working on ASP.NET Core, HTML, CSS and JavaScript in mind; you may need to adapt it to your own architecture and framework.

Why Speed Matters: Core Web Vitals

Website speed is a critical factor for user experience, SEO and conversion rates. A slow site makes visitors leave, and the business loses potential customers.

Google evaluates speed and user experience with three measurements called Core Web Vitals:

  • LCP (Largest Contentful Paint): How long it takes for the largest content element (usually the main image or heading) to appear. A good value is 2.5 seconds or less.
  • INP (Interaction to Next Paint): How quickly the page responds to user interactions (it replaced the older FID metric). A good value is 200 ms or less.
  • CLS (Cumulative Layout Shift): How much elements shift around while the page loads. A good value is 0.1 or less.

Almost every topic below serves to improve one of these three. Image optimization helps LCP, deferring scripts helps INP, and giving images a width and height fixes CLS.

Server and Infrastructure Optimization

A site's speed depends largely on the speed and configuration of its server.

  • Load balancing: For high-traffic sites, spread the load across multiple servers.
  • CDN: Serve static content (CSS, JS, images) from servers close to the user to cut latency.
  • HTTP/2 and HTTP/3: Make sure your server supports these protocols and switch them on; they offer multiplexing over a single connection and more efficient transfer.
  • Compression: Compress responses with Gzip or Brotli.
  • Environment management: Use the Development and Production environments properly in ASP.NET. With launchSettings.json you can test your work in the environment you want, and avoid configuration chaos and unexpected overrides when going live.

To enable response compression in ASP.NET Core:

C#
builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();
});

var app = builder.Build();

app.UseResponseCompression();

ASP.NET Core Performance Improvements

  • Asynchronous programming: Use async/await for database, file and network work; threads serve other requests instead of waiting, which improves scalability.
  • Do not fetch unnecessary data with EF Core: Avoid the N+1 query problem by loading related data in a single query with Include (eager loading), and fetch only the fields a given page needs.
  • Turn off the change tracker for read-only queries: AsNoTracking() reduces memory use and query time.
  • Per-page abstraction: A CQRS-like approach lets every page carry its own query and model. It means more files and classes, but it makes debugging and optimization easier. It is an approach I use often myself, but it is not mandatory for every project.
C#
public async Task<List<ArticleListItemDto>> GetLatestArticlesAsync(CancellationToken ct)
{
    return await _db.Articles
        .AsNoTracking()                          // no change tracking
        .Where(a => a.IsPublished)
        .OrderByDescending(a => a.PublishedAt)
        .Select(a => new ArticleListItemDto      // only the fields we need
        {
            Title = a.Title,
            Slug = a.Slug,
            AuthorName = a.Author.Name           // join in a single query
        })
        .Take(10)
        .ToListAsync(ct);
}

Database Optimization

Database queries largely determine an application's performance.

  • Query optimization: Remove unnecessary queries and fetch only the columns you need.
  • Indexing: Add suitable indexes to columns that are frequently filtered and sorted.
  • Caching: Cache frequently read, rarely changing data instead of querying the database on every request.
  • Choosing the right database: The data model and the workload decide, not speed. SQLite can be enough for a small site on a single server; as concurrent writes and users grow you need a server database such as PostgreSQL or SQL Server. Document databases such as MongoDB make sense when your data really is document-shaped; they are not a speed fix on their own.

How indexes work is covered in detail in SQL Server Indexes: Clustered, Non-Clustered and B-Tree, and ways to make EF Core queries lighter in EF Core Query Performance.

HTML, CSS and JavaScript Optimization

Well-structured, minified front-end code noticeably reduces page load time.

  • Minify and compress: Minify HTML, CSS and JS files and compress them with Gzip or Brotli.
  • Bundling: Combining several CSS and JS files reduces the number of requests. Under HTTP/2 and HTTP/3 the gain is smaller than it used to be, but a reasonable number of files is still good practice.
  • Critical CSS: Inline the CSS that builds the first view into the HTML and load the rest deferred.
  • Keep scripts in static files: Styles and scripts written inside a view are sent again with every page request and cannot be cached by the browser. Move them into static files referenced with script and link tags, and the browser downloads them once and reuses them from its cache on later pages. Splitting shared code into modules with export / import also reduces duplication.
  • Less, cleaner HTML: Avoid unnecessary wrapper elements. The "text-to-HTML ratio" that SEO tools often report is not something Google uses, but lean HTML makes the page smaller and the browser's work faster.
  • Use CSS for animations and keep JS to a minimum: CSS animations and transitions are lighter and keep the main thread freer.
  • Use CSS variables: Define constant values such as colors and font sizes once and reuse them everywhere to avoid repetition.
CSS
:root {
  --primary-color: #3498db;
  --secondary-color: #2ecc71;
  --font-size: 16px;
}

body {
  font-size: var(--font-size);
  color: var(--primary-color);
}

h1 {
  color: var(--secondary-color);
}

Note: If you keep variables in a separate file, load it with its own <link> tag in the HTML, or combine the files at build time, instead of pulling it in with @import inside CSS. @import makes the browser download files one after another and slows loading down.

Hints such as preload, preconnect and fetchpriority, which bring fonts, hero images and connections to other domains forward, are covered in Resource Hints.

Loading Scripts with Async and Defer

JavaScript files can block rendering of the page. There are two options:

  • defer: The script downloads in the background and runs after the HTML has been parsed, in the order it appears on the page. It is the safest and most predictable choice in most scenarios.
  • async: The script runs as soon as it has downloaded, regardless of order. It suits independent scripts that do not depend on each other, such as analytics.
HTML
<head>
  <!-- Minified CSS -->
  <link rel="stylesheet" href="/css/styles.min.css">

  <!-- defer when order matters -->
  <script src="/js/script.js" defer></script>

  <!-- async for independent scripts -->
  <script src="/js/analytics.js" async></script>
</head>

Image Optimization

Images are usually the largest and slowest-loading part of a page, and the element that most affects LCP is often an image.

  • Compression: Compress images with tools such as TinyPNG or JPEGoptim, or automatically at build or upload time.
  • Modern formats: WebP and AVIF deliver noticeably smaller files than JPEG and PNG.
  • Responsive images: Serve different image sizes for different screen widths.
  • Lazy loading: Load off-screen images only when needed. Do not lazy-load the main image in the first view (the LCP element), though; give it priority instead.
  • Dimensions: Give images width and height so the page does not shift while loading (CLS).
HTML
<!-- Responsive and lazy-loaded (image below the fold) -->
<img src="image-800.jpg"
     srcset="image-400.jpg 400w, image-800.jpg 800w"
     sizes="(max-width: 600px) 400px, 800px"
     width="800" height="450"
     loading="lazy"
     alt="Responsive image example">

<!-- Main hero image: not lazy, high priority -->
<img src="hero.jpg" width="1200" height="630" fetchpriority="high" alt="Hero image">

Caching Strategies

Caching significantly reduces page load time and server load. Think about it at two levels.

Browser Cache

Static files such as CSS, JS and images should get long-lived cache headers. When doing this, versioning file names (cache busting) is important so users receive the new version when a file changes. In ASP.NET Core, for static files:

C#
app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
    {
        // 1 year; safe when file names are versioned (e.g. site.css?v=hash)
        ctx.Context.Response.Headers.CacheControl = "public,max-age=31536000,immutable";
    }
});

In Razor views, the asp-append-version="true" attribute automatically adds a version parameter based on the file content. In a project hosted on IIS, the same setting can also be made in web.config:

XML
<configuration>
  <system.webServer>
    <staticContent>
      <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="365.00:00:00" />
    </staticContent>
  </system.webServer>
</configuration>

Caution: Adding Cache-Control: public, max-age=31536000 to all responses through httpProtocol/customHeaders can make dynamic HTML pages cached for a year too. Apply that header to static files only.

Server-Side Cache

Use server-side caching for pages that never or rarely change. A distributed cache such as Redis works at the data level, and ASP.NET Core's Output Caching at the page level:

C#
// Program.cs
builder.Services.AddOutputCache();
// ...
app.UseOutputCache();

// Controller
[OutputCache(Duration = 60)]
public IActionResult Index()
{
    return View();
}

Measurement and Testing Tools

You cannot improve what you do not measure. Commonly used tools:

  • Google PageSpeed Insights: Analyzes the page and provides Core Web Vitals results and improvement suggestions.
  • GTmetrix: Analyzes page speed with detailed reports.
  • WebPageTest: Tests the page from different locations and devices.
  • Lighthouse and Search Console: Lighthouse measures locally in the browser, while the Core Web Vitals report in Search Console shows real user data.

Speed Checklist

  1. Is response compression (Brotli/Gzip) on, and are HTTP/2 or HTTP/3 in use?
  2. Are static files served through a CDN with long-lived cache headers, and are file names versioned?
  3. Are database operations asynchronous, and do EF queries avoid unnecessary data and the N+1 problem?
  4. Are CSS and JS minified, and are scripts loaded with defer or async?
  5. Are images compressed, served as WebP/AVIF with srcset, and given dimensions?
  6. Is the main image (LCP) loaded eagerly with priority rather than lazily?
  7. Are frequently read, rarely changing data and pages cached?
  8. Are the Core Web Vitals in PageSpeed Insights and Search Console in the "good" range?

That completes the four-part SEO series. You can revisit the series from the first part.