Critical Resource Prioritization: Optimize Loading Order
Optimizing resource loading order is one of the biggest performance wins - prioritize what users see first.
Quick Navigation: Critical Rendering Path • Critical CSS Strategy • Resource Prioritization • Script Loading Strategy • Performance Impact • Implementation Checklist
Quick Decision Guide
Optimal Loading Order:
1. Inline critical CSS - Above-the-fold styles in <head> 2. Preload critical assets - Fonts, hero images 3. Defer non-critical CSS - Load async, apply on load 4. Defer JavaScript - Use defer attribute 5. Lazy load images - Below-fold with loading="lazy"
Result: 50-70% improvement in First Contentful Paint (FCP).
Critical Rendering Path
The Problem
<!-- ❌ Bad: Everything blocks rendering -->
<head>
<link rel="stylesheet" href="all-styles.css"> <!-- Blocks -->
<script src="analytics.js"></script> <!-- Blocks -->
<script src="app.js"></script> <!-- Blocks -->
</head>Result: Page blank until all resources load (2-4 seconds)
The Solution
<!-- ✅ Good: Prioritize critical path -->
<head>
<!-- Critical CSS inline -->
<style>
/* Only above-the-fold styles */
body { margin: 0; font-family: sans-serif; }
.header { background: #fff; height: 60px; }
</style>
<!-- Preload critical font -->
<link rel="preload" href="/font.woff2" as="font" crossorigin>
<!-- Defer non-critical CSS -->
<link rel="preload" href="/styles.css" as="style"
onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/styles.css"></noscript>
<!-- Defer scripts -->
<script defer src="/app.js"></script>
<script defer src="/analytics.js"></script>
</head>Result: Page visible in 0.5-1 second
Critical CSS Strategy
Extract Critical CSS
Use tools to identify above-the-fold styles:
# Using critical package
npm install critical
critical index.html --base dist --inline --minify > index-critical.htmlInline Critical, Defer Rest
<head>
<!-- Critical styles inline -->
<style>
.hero { /* above-fold styles only */ }
.header { /* ... */ }
</style>
<!-- Load full stylesheet async -->
<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>
</head>Next.js Example
// _document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
class MyDocument extends Document {
render() {
return (
<Html>
<Head>
{/* Critical CSS inline */}
<style dangerouslySetInnerHTML={{
__html: `
.hero { /* critical styles */ }
`
}} />
{/* Non-critical async */}
<link rel="preload" href="/styles.css" as="style"
onload="this.rel='stylesheet'" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}Resource Prioritization
Priority Order
<head>
<!-- 1. HIGHEST PRIORITY: Critical CSS -->
<style>/* Inline critical styles */</style>
<!-- 2. HIGH PRIORITY: Preload critical fonts -->
<link rel="preload" href="/font-regular.woff2" as="font" crossorigin>
<!-- 3. HIGH PRIORITY: Preload hero image -->
<link rel="preload" href="/hero.jpg" as="image">
<!-- 4. MEDIUM PRIORITY: Preconnect to API -->
<link rel="preconnect" href="https://api.example.com">
<!-- 5. LOW PRIORITY: Defer non-critical CSS -->
<link rel="preload" href="/styles.css" as="style"
onload="this.rel='stylesheet'">
<!-- 6. LOW PRIORITY: Defer scripts -->
<script defer src="/app.js"></script>
<script defer src="/analytics.js"></script>
<!-- 7. LOWEST: Prefetch next page -->
<link rel="prefetch" href="/dashboard.js">
</head>Font Loading Strategy
/* Prevent FOIT (Flash of Invisible Text) */
@font-face {
font-family: 'CustomFont';
src: url('/font.woff2') format('woff2');
font-display: swap; /* Show fallback immediately */
}<!-- Preload critical fonts -->
<link rel="preload" href="/font-bold.woff2" as="font"
type="font/woff2" crossorigin>Script Loading Strategy
Script Priorities
<!-- Critical: Inline small scripts -->
<script>
// Critical feature detection
if (!window.fetch) {
// Load polyfill
}
</script>
<!-- Important: Defer app code -->
<script defer src="/app.js"></script>
<!-- Low priority: Async analytics -->
<script async src="/analytics.js"></script>
<!-- Lowest: Load on idle -->
<script>
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
const script = document.createElement('script');
script.src = '/non-critical.js';
document.body.appendChild(script);
});
}
</script>Third-Party Scripts
<!-- Load after page interactive -->
<script>
window.addEventListener('load', () => {
// GTM
(function(w,d,s,l,i){...})(window,document,'script','dataLayer','GTM-ID');
});
</script>Performance Impact
Before Optimization
Timeline:
0ms - HTML requested
200ms - HTML received
400ms - CSS blocks rendering
800ms - Fonts load (FOIT)
1200ms - JS blocks
1500ms - First paint
2000ms - Interactive
FCP: 1.5s
TTI: 2.0sAfter Optimization
Timeline:
0ms - HTML requested
200ms - HTML received
300ms - Critical CSS (inline) renders
400ms - First paint (fonts with swap)
600ms - Full CSS loads
800ms - JS loads (defer)
1000ms - Interactive
FCP: 0.4s (73% faster)
TTI: 1.0s (50% faster)Real-World Results
E-commerce Site:
News Site:
Implementation Checklist
Critical Path Optimization
✅ Extract and inline critical CSS
✅ Preload critical resources
✅ Defer non-critical CSS
rel="preload" + onload trick<noscript>✅ Defer JavaScript
defer for app codeasync for independent scripts✅ Lazy load images
loading="lazy" for below-fold✅ Optimize fonts
font-display: swapMeasuring Impact
// Measure First Contentful Paint
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('FCP:', entry.startTime);
}
}).observe({ entryTypes: ['paint'] });
// Measure Time to Interactive
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log('TTI:', entry.processingStart);
}
}).observe({ entryTypes: ['first-input'] });