Frontend Engineering/
Lesson

When a user visits your site, the browser does not just fetch one file and display it. It fetches HTML, discovers CSS and JavaScript, fetches those, parses them, builds the DOMWhat is dom?The Document Object Model - the browser's live representation of your HTML page as a tree of objects that JavaScript can read and modify. and CSSOM, executes scripts, and finally paints pixels on screen. Every step in this chain is an opportunity for something to block the process and make your user wait.

Understanding loading strategies is about controlling this pipelineWhat is pipeline?A sequence of automated steps (install, lint, test, build, deploy) that code passes through before reaching production., making sure the browser does the most important work first and defers everything else.

The critical rendering path

The critical rendering path is the sequence of steps the browser takes before it can show the first pixel on screen. In simplified terms:

  1. Parse HTML, build the DOMWhat is dom?The Document Object Model - the browser's live representation of your HTML page as a tree of objects that JavaScript can read and modify.
  2. Discover and fetch CSS, build the CSSOM
  3. Discover and fetch JavaScript, execute it
  4. Combine DOM + CSSOM into the render tree
  5. Paint pixels on screen

The problem is that CSS and synchronous JavaScript block this pipelineWhat is pipeline?A sequence of automated steps (install, lint, test, build, deploy) that code passes through before reaching production.. The browser will not paint anything until all render-blockingWhat is render-blocking?A resource (CSS or synchronous JavaScript) that prevents the browser from painting anything on screen until it's fully downloaded and processed. CSS is loaded and parsed, and it will pause HTML parsing whenever it encounters a synchronous <script> tag.

<!-- This page won't render until BOTH CSS files are fully loaded -->
<head>
  <link rel="stylesheet" href="/styles.css" />
  <link rel="stylesheet" href="/animations.css" />  <!-- Blocks render -->
  <script src="/analytics.js"></script>               <!-- Blocks parsing -->
</head>
AI pitfall
When AI generates HTML pages, it dumps every stylesheet and script into <head> with no loading strategy. A page with three CSS files and two scripts in the head can take 3-5 extra seconds to render, even if the HTML and content are tiny. Always audit what AI puts in the <head>.
02

Script loading: async vs defer vs moduleWhat is module?A self-contained file of code with its own scope that explicitly exports values for other files to import, preventing name collisions.

Where and how you load JavaScript has a massive impact on rendering speed. Here are your options:

StrategyBlocks parsing?Execution orderBest for
<script> (default)YesIn orderNothing, avoid this
<script async>NoWhen downloadedIndependent scripts (analytics)
<script defer>NoIn DOM orderScripts that need the DOM
<script type="module">No (deferred by default)In DOM orderModern ES modules
<!-- Bad: blocks HTML parsing until fully loaded and executed -->
<script src="/heavy-library.js"></script>

<!-- Good: loads in parallel, executes after HTML is parsed -->
<script defer src="/app.js"></script>

<!-- Good: loads in parallel, executes as soon as downloaded -->
<script async src="/analytics.js"></script>

<!-- Good: ES modules are deferred by default -->
<script type="module" src="/app.mjs"></script>

The rule of thumb: use defer for your application scripts (they usually need the DOMWhat is dom?The Document Object Model - the browser's live representation of your HTML page as a tree of objects that JavaScript can read and modify.) and async for independent third-party scripts like analytics that do not depend on DOM order.

When async goes wrong

There is a subtle trap with async: execution order is not guaranteed. If script B depends on script A, and both are loaded with async, script B might execute first and crash.

<!-- Dangerous: jQuery might not be loaded when plugin executes -->
<script async src="/jquery.js"></script>
<script async src="/jquery-plugin.js"></script>

<!-- Safe: both execute in DOM order after HTML parsing -->
<script defer src="/jquery.js"></script>
<script defer src="/jquery-plugin.js"></script>
AI pitfall
When AI generates code that adds third-party scripts, it either uses plain <script> tags (blocking) or slaps async on everything (risking execution order bugs). It rarely considers dependencies between scripts. Always verify that scripts with dependencies use defer, not async.
03

Resource hints

Resource hints are HTML tags you add to <head> that tell the browser to take action on certain resources before it discovers them naturally. Think of them as giving the browser a headWhat is head?A special pointer in Git that indicates the commit you are currently working on - usually the tip of the active branch. start.

<head>
  <!-- Preload: "I need this NOW for this page" -->
  <link rel="preload" href="/fonts/main.woff2" as="font" crossorigin />
  <link rel="preload" href="/hero.webp" as="image" />

  <!-- Preconnect: "I'll need something from this domain soon" -->
  <link rel="preconnect" href="https://api.example.com" />

  <!-- Prefetch: "The user will probably visit this page next" -->
  <link rel="prefetch" href="/dashboard" />

  <!-- DNS prefetch: "Just resolve the DNS early" -->
  <link rel="dns-prefetch" href="https://fonts.googleapis.com" />
</head>
HintWhat it doesWhen to use
preloadFetches a resource immediately with high priorityLCP images, critical fonts, above-fold CSS
preconnectOpens a connection to a domain earlyAPIs, CDNs, third-party services you will use
prefetchFetches a resource at low priority for future navigationLikely next pages
dns-prefetchResolves DNS onlyThird-party domains you will contact
Use preload sparingly. Preloading too many resources competes for bandwidth and can actually slow down the things that matter most. Limit it to 2-3 truly critical resources.
04

Lazy loadingWhat is lazy loading?Deferring the loading of a resource like an image or component until the moment it's actually needed, speeding up the initial page load. images

Images below the fold should not load until the user scrolls near them. The loading="lazy" attribute makes this trivial:

<!-- Hero image: load immediately -->
<img src="hero.webp" loading="eager" fetchpriority="high"
     width="1200" height="600" alt="Hero" />

<!-- Below-fold images: load when user scrolls near them -->
<img src="product-1.webp" loading="lazy" width="400" height="300" alt="Product 1" />
<img src="product-2.webp" loading="lazy" width="400" height="300" alt="Product 2" />
<img src="product-3.webp" loading="lazy" width="400" height="300" alt="Product 3" />
Never put loading="lazy" on your LCP image. That image should load as early as possible, so use loading="eager" or omit the attribute entirely.
05

Lazy loadingWhat is lazy loading?Deferring the loading of a resource like an image or component until the moment it's actually needed, speeding up the initial page load. React components

For JavaScript components, React's lazy() and Suspense let you split heavy code into separate chunks that load on demand:

import { lazy, Suspense } from 'react';

// These won't be in the initial bundle
const Chart = lazy(() => import('./Chart'));
const DataTable = lazy(() => import('./DataTable'));

function Dashboard({ activeTab }) {
  return (
    <div>
      {activeTab === 'chart' && (
        <Suspense fallback={<div>Loading chart...</div>}>
          <Chart />
        </Suspense>
      )}
      {activeTab === 'table' && (
        <Suspense fallback={<div>Loading table...</div>}>
          <DataTable />
        </Suspense>
      )}
    </div>
  );
}
06

CSS loading strategies

CSS is render-blockingWhat is render-blocking?A resource (CSS or synchronous JavaScript) that prevents the browser from painting anything on screen until it's fully downloaded and processed. by default, but not all CSS is critical for the first paint. You can split critical CSS (above-the-fold styles) from non-critical CSS (styles for content below the fold or for specific interactions).

<!-- Critical CSS: inlined for instant first paint -->
<style>
  /* Only above-the-fold styles */
  header { display: flex; align-items: center; }
  .hero { max-width: 1200px; margin: 0 auto; }
</style>

<!-- Non-critical CSS: loaded without blocking render -->
<link rel="preload" href="/full-styles.css" as="style"
      onload="this.onload=null;this.rel='stylesheet'" />
<noscript><link rel="stylesheet" href="/full-styles.css" /></noscript>

Media-specific stylesheets

Stylesheets with a media attribute only block rendering when the media queryWhat is media query?A CSS rule that applies styles only when certain conditions are true, like the screen being wider than 768 pixels. matches. This is a free optimization for print styles and other conditional CSS:

<!-- Blocks render - this is for screen -->
<link rel="stylesheet" href="/styles.css" />

<!-- Does NOT block render - only loaded for print -->
<link rel="stylesheet" href="/print.css" media="print" />

<!-- Does NOT block render on desktop - only for mobile -->
<link rel="stylesheet" href="/mobile.css" media="(max-width: 600px)" />
07

Putting it all together

A well-optimized <head> section looks like this:

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <!-- Preconnect to critical third-party origins -->
  <link rel="preconnect" href="https://fonts.googleapis.com" />

  <!-- Inline critical CSS -->
  <style>/* above-the-fold styles */</style>

  <!-- Preload LCP image and critical font -->
  <link rel="preload" href="/hero.webp" as="image" />
  <link rel="preload" href="/fonts/main.woff2" as="font" crossorigin />

  <!-- Non-critical CSS loaded asynchronously -->
  <link rel="preload" href="/styles.css" as="style"
        onload="this.onload=null;this.rel='stylesheet'" />

  <!-- Print CSS with media attribute - doesn't block render -->
  <link rel="stylesheet" href="/print.css" media="print" />

  <!-- App script deferred -->
  <script defer src="/app.js"></script>

  <!-- Analytics loaded async (independent) -->
  <script async src="/analytics.js"></script>
</head>

Compare that to what AI typically generates:

<!-- What AI generates - no loading strategy at all -->
<head>
  <link rel="stylesheet" href="/styles.css" />
  <link rel="stylesheet" href="/animations.css" />
  <link rel="stylesheet" href="/vendor.css" />
  <script src="/vendor.js"></script>
  <script src="/app.js"></script>
  <script src="/analytics.js"></script>
</head>

The first example starts rendering almost immediately. The second blocks rendering until six files are fully downloaded and processed. Same content, dramatically different user experience.

AI pitfall
AI-generated <head> sections are consistently the worst part of AI output from a performance perspective. AI treats the head as a dumping ground for every dependency, with no thought to loading order, render blocking, or resource prioritization. After any AI generates an HTML page, the first thing you should audit is the <head>.
javascript
// Loading strategy patterns

// 1. Route-based code splitting in React
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

// 2. Intersection Observer for custom lazy loading
function useLazyLoad(ref) {
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.isIntersecting) {
          setIsVisible(true);
          observer.disconnect();
        }
      },
      { rootMargin: '200px' }
    );

    if (ref.current) observer.observe(ref.current);
    return () => observer.disconnect();
  }, [ref]);

  return isVisible;
}