Secret shortcuts of Loading
Web Performance
Start with why
1 seconds slowness > 10% less users
The BBC found they lost an additional 10% of users for every additional second their site took to load.
100ms faster > 1% more conversions
For Mobify, every 100ms decrease in homepage load speed worked out to a 1.11% increase in session-based conversion, yielding an average annual revenue increase of nearly $380,000.
20% faster > 10% more conversions
Retailer Furniture Village audited their site speed and developed a plan to address the problems they found, leading to a 20% reduction in page load time and a 10% increase in conversion rate.
40% faster > 15% more sign-ups
Pinterest reduced perceived wait times by 40% and this increased search engine traffic and sign-ups by 15%.
Core Web Vitals
Largest Contenful Paint (LCP)
First Input Delay (FID)
Cumulative Layout Shift (CLS)
Performance pipeline
Loading
performance
Performance of
performance
JavaScript
Rendering
Loading Performance
Getting files you need over the network
Fetching resources over the network can be slow and expensive
Depends on
your internet speed
size of resource files
location of the server
request roundtrips
user devices
Nikola Mitrović
Development Lead & Technical Architect
Vega IT
Novi Sad, Serbia
Nikola Mitrović
Development Lead & Technical Architect
Vega IT
Novi Sad, Serbia
The cost of JavaScript
Large bundle sizes affect almost all Web Vitals metrics
JavaScript is the biggest bottleneck behind slow apps
Large bundle size will likely cause delays during
load
render
user interaction
page scroll
Optimize Loading JS
Minifying the JavaScript
Compressing the JavaScript using gzip or brotli
Removing unused code via tree shaking
Analyze bundle size with Webpack / Vite
Check lib size with Bundlephobia
Import cost VSC plugin
Use & attributes for third-party scripts
async
defer
Code Splitting
import React, { Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import FirstPage from './components/FirstPage/FirstPage';
import RocketComponent from './components/Rocket/Rocket';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<FirstPage />} />
<Route
path="/rocket"
element={<RocketComponent />}
/>
</Routes>
</BrowserRouter>
);
}
export default App;
Route-based Code Splitting
Route-based Code Splitting
import React, { Suspense, lazy } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import FirstPage from './components/FirstPage/FirstPage';
const RocketLoader = lazy(() => import('./components/RocketLoader/RocketLoader'));
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<FirstPage />} />
<Route
path="/rocket"
element={
<Suspense fallback={<div>Loading...</div>}>
<RocketLoader />
</Suspense>
}
/>
</Routes>
</BrowserRouter>
);
}
export default App;
Component-based Code Splitting
import React, { Suspense, lazy, useRef } from 'react';
import { useVisible } from './hooks/useVisible';
import logo from './assets/images/omega.png';
import astronaut from './assets/images/astronaut.jpg';
import RocketComponent from "./components/Rocket/Rocket";
const RocketLoader = lazy(() => import("./components/RocketLoader/RocketLoader"));
function App() {
const sectionTwoRef = useRef<HTMLDivElement>();
const isVisible = useVisible<boolean>(sectionTwoRef);
return (
<>
<div className="section section-first">
<img src={astronaut} className="astronaut" alt="astronaut" />
</div>
<div ref={sectionTwoRef} className="section section-second">
<RocketComponent />
</div>
{isVisible && (
<Suspense fallback={<div>Loading...</div>}>
<RocketLoader />
</Suspense>
)}
</>
);
}
export default App;
Reduce usage of libs
import { useState, useEffect, RefObject } from 'react';
export const useVisible = (
ref: RefObject<HTMLDivElement>,
options = {
root: null,
rootMargin: '0px',
threshold: 1.0
}
) => {
const [isVisible, setIsVisible] = useState<boolean>(false);
useEffect(() => {
const { current: elementRef } = ref;
if (elementRef) {
const observer = new IntersectionObserver(
([entry]) => setIsVisible(entry.isIntersecting), options
);
observer.observe(elementRef);
return () => observer.unobserve(elementRef);
}
}, [ref, options]);
return isVisible;
};
Optimize Loading Images
Use tag over background-image
Take advantage of next-gen formats
Create responsive images with
Support resolution-switching with
Load key images fast with
fetchpriority
sizes
srcset
<img>
Lazy loading & async decoding
Compress images
.my-pic {
background-image: url("my-pic.png");
}
<img src="my-pic.png" />
<img src="large.webp" />
<img srcset="small.webp 500w, \
medium.webp 1000w, \
large.webp 2000w"
src="large.webp"
/>
<img src="large.webp" />
<img srcset="small.webp 400w, \
large.webp 900w"
sizes="(max-width: 400px) 95vw, \
(max-width: 900px) 50vw"
src="large.webp"
/>
<img src="img.webp" />
<img src="bellow-the-fold=img.webp" />
<img src="img.webp" />
<img src="bellow-the-fold=img.webp"
loading="lazy"
decoding="async"
/>
<img src="image.png" />
<img src="image.jpg" />
<img src="image.webp" />
<img src="image.avif" />
<img src="above-the-fold-img.webp" />
<img src="img.webp" />
<img src="above-the-fold-img.webp"
fetchpriority="high"
/>
<img src="img.webp" />
import React from "react";
import RocketLoader from "./components/RocketLoader/RocketLoader";
import space from "./assets/images/space-background.jpg";
import galaxy from "./assets/images/galaxy.jpeg";
function App() {
return (
<div className="App">
<img src={galaxy} className="galaxy" />
<div className="section section-first">
<img src={space} alt="logo" />
</div>
<div className="section section-second">
<RocketLoader />
</div>
</div>
);
}
export default App;
Progressive image loading (LQIP pattern)
import React from 'react';
import ProImg from './components/ProImg/ProImg';
import RocketLoader from './components/RocketLoader/RocketLoader';
import space from "./assets/images/space-background.jpg";
import galaxy from './assets/images/galaxy.jpeg';
import galaxyLight from './assets/images/galaxy-light.jpeg';
function App() {
return (
<div className="App">
<ProImg
src={galaxy}
fallback={galaxyLight}
/>
<div className="section section-first">
<img src={space} alt="logo" />
</div>
<div className="section section-second">
<RocketLoader />
</div>
</div>
);
}
export default App;
Progressive image loading (LQIP pattern)
import React from 'react';
import ProImg from './components/ProImg/ProImg';
import RocketLoader from './components/RocketLoader/RocketLoader';
import space from "./assets/images/space-background.jpg";
import galaxy from './assets/images/galaxy.jpeg';
import galaxyLight from './assets/images/galaxy-light.jpeg';
function App() {
return (
<div className="App">
<ProImg
src={galaxy}
fallback={galaxyLight}
/>
<div className="section section-first">
<img src={space} alt="logo" />
</div>
<div className="section section-second">
<RocketLoader />
</div>
</div>
);
}
export default App;
import React, { useState, FC } from 'react';
import ProImgProps from './ProImgProps'
import './LazyImg.css';
const ProImg = ({ src, className, alt, fallback }: ProImgProps) => {
const [imgLoaded, setLoaded] = useState<boolean>(false);
const handleImgLoaded = () => setLoaded(true);
return (
<picture className="progressive-img">
{!imgLoaded && (
<img
src={fallback}
className={`progressive-img__small ${className}`}
alt={alt}
/>
)}
<img
src={src}
className={`progressive-img__large ${className}`}
alt={alt}
onLoad={handleImgLoaded}
/>
</picture>
);
};
export default ProImg;
Progressive image loading (LQIP pattern)
Optimize Loading Data
Reduce data payload with GraphQL
Prefetch data
<link rel="preload" href="/api/data" as="fetch" crossorigin="anonymous">
Compress data payloads
Paging and filtering
Prefetching data via useSWR
import React, { Suspense, useState } from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import { mutate } from 'swr';
import FirstPage from './components/FirstPage/FirstPage';
import LaunchesPage from './components/LaunchesPage/LaunchesPage';
import RocketLoader from './components/RocketLoader/RocketLoader';
function App() {
const handlePrefetch = async () => {
const response = (await fetch('https://api.nasa.gov/planetary/apod')).json();
await mutate('https://api.nasa.gov/planetary/apod', response);
};
return (
<Suspense fallback={<RocketLoader />}>
<BrowserRouter>
<Routes>
<Route
path="/"
element={
<FirstPage>
<Link to="rocket" onMouseEnter={handlePrefetch}>
Go to Rocket List Page
</Link>
</FirstPage>
}
/>
<Route path="/rocket" element={<LaunchesPage />} />
</Routes>
</BrowserRouter>
</Suspense>
);
}
export default App;
import React, { Suspense, useState } from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import { mutate } from 'swr';
import FirstPage from './components/FirstPage/FirstPage';
import LaunchesPage from './components/LaunchesPage/LaunchesPage';
import RocketLoader from './components/RocketLoader/RocketLoader';
function App() {
const handlePrefetch = async () => {
const response = (await fetch('https://api.nasa.gov/planetary/apod')).json();
await mutate('https://api.nasa.gov/planetary/apod', response);
};
return (
<Suspense fallback={<RocketLoader />}>
<BrowserRouter>
<Routes>
<Route
path="/"
element={
<FirstPage>
<Link to="rocket" onMouseEnter={handlePrefetch}>
Go to Rocket List Page
</Link>
</FirstPage>
}
/>
<Route path="/rocket" element={<LaunchesPage />} />
</Routes>
</BrowserRouter>
</Suspense>
);
}
export default App;
import React, { FC } from 'react';
import { useSWRConfig } from 'swr';
import { Container, Grid, Typography } from '@material-ui/core';
import FlightInfo from '../FlightInfo/FlightInfo';
const LaunchesPage: FC = () => {
const { cache } = useSWRConfig();
const flights = cache.get('https://api.nasa.gov/planetary/apod');
return (
<Container maxWidth="lg">
<p>
Welcome to <span>Svemirko Flights!</span>
</p>
<p>Explore Svemirko's recent flights</p>
<Grid container justifyContent="flex-start" spacing={7}>
{fligths?.map((flight) => (
<FlightInfo key={flight.id} flight={flight} />
))}
</Grid>
</Container>
);
};
export default LaunchesPage;
Prefetching data via useSWR
Caching
HTTP Cache eliminates network latency
Only affects "safe" HTTP methods
Cache-control headers
no-store
no-cache
max-age
Limited control
stale-while-revalidate
Content-Addressable Storage pattern
Service Workers
More control over cache
Proxy between browser and network
Different kind of scenarios
Offline capabilities + performance
Server Side Rendering
SSR Frameworks
Generate server side data at runtime
Generate server side data at runtime
Nested components with router outlet
Layouts
Stale While Revalidate cache control
Stale While Revalidate cache control
Prefetching via <Head> & <Link> components
Prefetching
Edge Computing
0kb JavaScript
Resumability
Code splitting + preloading
Lazy creation of event handlers
Serialized data
Islands architecture
Server-first
Zero JS, by default
UI agnostic
0kb JavaScript
Server side components
Serialized data
Component-specific data processing
Streams the result of data fetching
Best performance hack ever
setTimeout(() => {}, 5000);
With great power comes great responsibility
Green JavaScript
Internet is consuming 21% of the electricity
check out carbon footprint
website traffic
energy intensity
data transfers
Thank you!
n.mitrovic@vegait.rs
You can find me at
Link to the slides
Secret Shortcuts of Loading Web Performance
By nmitrovic
Secret Shortcuts of Loading Web Performance
- 631