Web Performance Basics (Overview, Code Optimization, Caching, Images, CDN, N+1 Problem, Webpack/lodash)

Hanwen Zhang
7 min readOct 29, 2022

Always keep performance in mind during development.

How Browser Render Content:

  1. Send the request along with your IP address to DNS Lookup (phone book for the web)
  2. ISP (Internet Service Providers) cached DNS info — based on your IP address near you
  3. TCP handshake: the temporary connection that works together, to exchange information like tokens
  4. The browser sends HTTP GET request, TTFB, time to the first byte, 14-kilobyte first, then double up.
  5. Document — DOM tree, CSSOM tree
  6. Content sent through CDN (Content Delivery Network) from server to ISP

The PRPL pattern — is used to build scalable, fast modern web apps with great user experience.

  • Push (or preload) the most important resources
  • Render the initial route as soon as possible
  • Pre-cache remaining assets
  • Lazy load of other routes and non-critical assets
Photo by Austin Distel on Unsplash

Code Optimization

How do you generally improve performance?

  • use uglify and minify to reduce the bundle size
  • use lazy loading to improve the page loading speed.
  • use a content delivery network to improve the loading speed.
  • React.lazy and React.suspense support lazy loading with webpack.
  • React.memo shouldComponentUpdate logic to reduce unnecessary re-rendering, improve the component rendering performance
  • Event Delegation (allows you to avoid adding event listeners to specific nodes)

JavaScript Optimization

  • Minify to reduce the size
  • Uglify to improve code efficiency
  • Code split and use ESM modules when possible

Ideal JavaScript Loading

  • to start, load only critical JS to get the app framework up and running
  • next, load the necessary JS modules for functionality
  • conditionally import ESM modules only when needed (lazy loading)

JavaScript loading: Best Practices

  • place <script> tag in <head>
  • use async as the default, loading JS and no need to wait for the whole DOM to be created first
  • use defer scripts that need a fully built DOM, still loading the script asynchronously without render-blocking. And then we literally defer the execution of the JavaScript until the HTML parsing is complete.

Difference between <script>, <script async> and <script defer>.

<script>

  • HTML parsing is blocked when the script is read.
  • the script is fetched and executed immediately, and HTML parsing resumes after the script is executed.
  • If put the script tag in the end, the JS script is not able to be downloaded until the browser reads it

<script async>

  • in parallel to HTML parsing and executed as soon as it is available (potentially before HTML parsing completes)

<script defer>

  • ensures the entire body loads before running JavaScript
  • in parallel to HTML parsing and executed when the page has finished parsing, ensuring that the HTML is fully parsed before executing.
  • There’s not much difference in putting a normal <script> at the end of <body>.

Minifying and uglifying JavaScript

  • make your code prettier, make it more efficient during compiling phase
  • remove unnecessary code
  • rename to a more efficient version for the machine
  • save time on loading
  • need human-readable code but transfer to computer-readable code
  • npm terser — make js as small as possible

Lazy-loading JavaScript modules with import()

  • promise
  • async-await function
  • pulling in an external library

What is JavaScript Webpack?

A static module bundler for the front-end development of JS applications -> bundle your styles (bundle your JavaScript files together) — It takes all the code from your application and makes it usable in a web browser.

Webpack Examples

  • HMR(Hot Module Replacement): Update the page directly without a full page reload — a more efficient dev environment and will not lose the current state
  • Tree Shaking: Get rid of unnecessary code
  • Code Splitting: Split your modules properly according to the dependency graph
  • Lazy Loading: Split your code at logical breakpoints, and load certain parts of the component tree only when it's in use.

Similar bundling vs modules: Snowpack, Parcel, Rollup

lodash

Debounce and throttle are techniques to control how many times we allow a function to be executed over time

  • debounce -> search bar (auto-complete)
  • throttle -> scrolling / resizing page
  • debounce / throtte -> web performance improvement -> control the number of times the function will be called

debounce

  • setTimeout
  • continuously execute when event change ends, only execute once after event change stop
  • like a search bar, you enter text, once yoou finish, wait for the timer done, it will send the request only one time to UI after the time period
  • “group” multiple sequential calls in a single one, only send the final one
  • const debouncedFunc = _.debounce(fetchAPI, 100); onUserInput => { debouncedFunc() } - shorter than a 100

throtte

  • setInterval
  • continuously execute when event change happens
  • like resizing page, you send requests to the UI with a timer interval, will be sent no matter how many requests within the time period
  • _.throttle(fetchAPI, 100);

Virtual Scrolling

  • While the user is scrolling the table, the Grid requests and displays only the visible pages.
  • Import component. Import VirtualScroll from “react-dynamic-virtual-scroll”. Add component as follows in your render method:
  • <VirtualScroll className="List" minItemHeight={40} totalLength={100} renderItem={(rowIndex) => { return ( <div className="List-item"> <h3>List item: {rowIndex}</h3> </div> ); }} />

Dynamic Programming (Caching — Storing Assets)

  • Cache stores the function for reusability
  • Cache-Control: instruction of request and response cache;
  • Redis: in-memory data structure store (server), used as a NoSQL key-value persistent database, cache, and message broker.

Server-side Caching

  • Vital for server-side rendered content from a content management system (cms) like WordPress, Drupal, etc
  • Caching dynamic assets means the server does not have to generate the same assets for every request
  • Beware of dynamic asset updates

Browser Caching

  • browsers cache files automatically
  • you can control browser caching using HTTP headers
  • caching strategies are now often controlled by the CDN
  • splitting JS and CSS bundles into multiple files means updates do not require re-downloading and re-caching large files

Automatic Browser Caching

  • CSS
  • JavaScript
  • Images

CDN caching

Extend the caching strategy provided by your host and CDN.

  • CDNs are effectively external caching for assets
  • Serve cached versions of the site from the server closest to the visitor
  • Edge computing moves dynamic asset building to the CDN for quicker and closer service

A CDN is a content delivery network connected to your hosting server.

  • Without a CDN, every visitor has to get files from your server.
  • With a CDN, the CDN grabs a copy of your site, caches it, and distributes it to all of its distributed servers, and the visitor gets a copy from whatever CDN server is closest to them, that way, the user gets much faster access to your site and the load on your hosting provider is far less severe.

As an example, Cloudflare, a popular CDN can be configured to automatically generate and save WebP versions of all images on your site, and then serve them to browsers supporting that image format.

Optimizing Delivery

Compress data with Gzip and Brotli (Brotli takes longer than Gzip)

Link: preload, preconnect, prefetch, prerender

  • preload — loads high prior sources that need to be loaded faster ;
  • preconnect — If some resources are required to accelerate the handshake, use them to reduce latency;
  • prefetch — loads low prior resources and cache ;
  • DNS-prefetch — reduces latency of resolving domain names before resources get requested ;
  • prerender — similar to prefetch + caches whole the page ;

Preloading vs prefetch

  • When we know the browser will need an asset, we can preload that asset into the browser cache before the browser needs it.
  • prefetch: lower priority

Bottlenecks

By far the biggest bottleneck for any modern site or service is the IMAGES

Improve image performance:

  • optimizing images to reduce their file size
  • using responsive images markup
  • lazy loading images so they’re only loaded when the browser actually needs them
  • use fewer images or not use images at all
  • Cache: Use CDN (content delivery network) for distributing static data;
  • Lazy Load images and videos — Use <img loading="lazy"/> or libraries like lazysizes;

Image format options

  • JPG/JPEG — photo, complex graphic
  • PNG — complex graphics, transparency
  • GIF — avoid animated, use a video instead
  • SVG — scalable graphics, icons, graphs (simple)
  • WebP — photo, transparency

Automated Image Optimization

  • npm — imagemin
  • npm- squooch cli
  • npm — sharp

from HTML to CSS to JavaScript, and images and everything else can and should be compressed and optimized as much as possible to make the physical data transfer as small as possible.

Avoid N+1 Query Problem

  • The N+1 issue creates significant performance problems when an additional query is performed to fetch related data.
  • Eager loading is the most common solution to the N+1 query problem by fetching related data along with the main data in a single query. In SQL, use JOIN clauses to fetch related data in a single query.
  • Batch Loading involves fetching related data in batches instead of one item at a time.
  • Cache reduces the need for repetitive database queries.
  • Pagination allows limiting the number of entities fetched at once.
  • Database Indexes allow the database engine to find and retrieve data more efficiently.
  • Add .includes() for ruby-on-rails

HTTP/2 and Multiplexing

HTTP/1.1: Synchronous loading (slow), need to be parallel connections to the server to pull down the server => Head of line blocking, where the first file, the HTML file, holds back the rest of the files from downloading.

It also puts enormous strain on the internet connection and the infrastructure, both the browser and the server, because you’re now operating with six connections instead of one single connection.

HTTP/2, we have what’s known as multiplexing. The browser can download many separate files at the same time over one connection, and each download is independent of the others. That means with HTTP/2, the browser can start downloading a new asset as soon as it’s encountered, and the whole process happens significantly faster.

--

--

Hanwen Zhang

Full-Stack Software Engineer at a Healthcare Tech Company | Document My Coding Journey | Improve My Knowledge | Share Coding Concepts in a Simple Way