Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Front-End Performance Optimization: Core Techniques and Key Metrics

Notes 1

Performance optimization is a structured discipline where strategies often build upon one another. The process can be categorized into network-level and rendering-level optimization, while the outcome targets time and volume reduction. The core objective is to deliver website content to users swiftly and accurately.

All optimization efforts revolve around two core layers (network, rendering) and two auxiliary layers (time, volume), with the latter being integrated within the former. This article outlines nine core strategies and six key metrics for front-end performance optimization, defined here to establish a framework for systematic improvement.

Nine Core Strategies

Network-Level Optimization

This focuses on making resources smaller and faster to load, addressed through four key areas:

  • Build Strategy: Leveraging tools like Webpack, Rollup, Vite, etc.
  • Image Strategy: Optimizing based on image types (JPG, PNG, SVG, WebP, Base64).
  • Distribution Strategy: Utilizing a Content Delivery Network (CDN).
  • Caching Strategy: Implementing browser caching (Strong Cache, Negotiation Cache).

These steps are sequential, spanning development (Build & Image strategies) and production (Distribution & Caching strategies).

Build Strategy

Primarily using Webpack, this strategy addresses both time and volume reduction. The following are twelve optimization techniques, categorized accordingly (⏱ for time, 📦 for volume).

Reduce Build Time

  • Narrow Scope: Use include/exclude in loaders to limit file search paths, avoiding unnecessary processing of node_modules.
// webpack.config.js example
module.exports = {
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        include: path.resolve(__dirname, 'src'),
        exclude: /node_modules/,
        use: 'babel-loader'
      }
    ]
  }
};
  • Cache Outputs: Configure loaders/plugins to cache results, recompiling only changed files.
// Example with babel-loader and eslint-webpack-plugin
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: { cacheDirectory: true }
        }
      }
    ]
  },
  plugins: [
    new (require('eslint-webpack-plugin'))({ cache: true })
  ]
};
  • Resolve Aliases: Configure resolve.alias and resolve.extensions to speed up module resolution.
module.exports = {
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
      'Components': path.resolve(__dirname, 'src/components')
    },
    extensions: ['.js', '.jsx', '.json']
  }
};
  • Pre-Build Dependencies: Use DllPlugin to separately bundle infrequently changed third-party libraries. (Note: This is less critical with modern Webpack versions but can still help large projects).
  • Parallel Processing: Use thread-loader to enable multi-threaded processing for CPU-intensive tasks (use only for large projects).
const os = require('os');
module.exports = {
  module: {
    rules: [
      {
        test: /\.js$/,
        use: [
          { loader: 'thread-loader', options: { workers: os.cpus().length } },
          { loader: 'babel-loader', options: { cacheDirectory: true } }
        ]
      }
    ]
  }
};
  • Visualize Bundle: Use webpack-bundle-analyzer to identify large dependencies and optimization opportunities.

Reduce Bundle Volume

  • Code Splitting: Split code using splitChunks to extract common modules.
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          priority: 10
        },
        common: {
          minChunks: 2,
          name: 'common',
          priority: 5,
          reuseExistingChunk: true
        }
      }
    },
    runtimeChunk: { name: 'runtime' }
  }
};
  • Tree Shaking: Remove unused code. Ensure module syntax is ES6 (import/export) and set mode: 'production'.
  • Dynamic Polyfilling: Use a service like polyfill.io to serve polyfills based on the user's browser UA, rather than bundling all polyfills.
const HtmlWebpackTagsPlugin = require('html-webpack-tags-plugin');
module.exports = {
  plugins: [
    new HtmlWebpackTagsPlugin({
      tags: ['https://polyfill.io/v3/polyfill.min.js'],
      append: false
    })
  ]
};
  • Lazy Loading: Dynamically import route components or features using import() to reduce initial bundle size.
// React route example
const Dashboard = React.lazy(() => import('./views/Dashboard'));
// Webpack will create a separate chunk via magic comment
const Settings = () => import(/* webpackChunkName: "settings" */ './views/Settings');
  • Scope Hoisting: In production mode (mode: 'production'), Webpack concatenates modules into fewer scopes, reducing function wrappers and memory overhead.
  • Resource Compression: Minify HTML, CSS, JS, and compress images.
    • HTML: Use html-webpack-plugin with minify option.
    • CSS/JS: Use css-minimizer-webpack-plugin and terser-webpack-plugin.
    • Images: Use tools like imagemin-webpack-plugin or process images before deployment.
Image Strategy

Choose the appropriate image format and compress all images before production.

Format Size Quality Transparency Best For
JPEG Small Medium No Photos, complex images
PNG Large High Yes Icons, graphics with transparency
SVG Small High Yes Icons, vector graphics
WebP Small Medium Yes Modern browsers, replacing JPEG/PNG
Base64 Variable Medium Yes Very small inline icons

Use tools like Squoosh, TinyPNG, or ImageOptim for compression.

Distribution Strategy
  • Serve all static assets (CSS, JS, fonts, images) via a CDN.
  • Host static assets on a different domain than your main page to avoid sending cookies with requests. A CDN caches resources on geographically distributed servers, reducing latency by serving content from the nearest location to the user.
Caching Strategy

Leverage browser caching through HTTP headers. The flow is: Strong Cache -> Negotiation Cache.

  • Strong Cache: Use Cache-Control (e.g., max-age=31536000 for immutable resources) or Expires. The browser uses the local copy without contacting the server.
  • Negotiation Cache: Use Last-Modified/ETag headers. The browser validates with the server (304 Not Modified if unchanged).

Scenarios:

  • Frequently changing resources: Cache-Control: no-cache with ETag.
  • Immutable resources: Cache-Control: max-age=31536000, immutable. Use file name hashing so updates fetch new files.

Rendering-Level Optimization

This focuses on making code parse and execute faster through five areas:

  • CSS Strategy: Follow CSS best practices.
  • DOM Strategy: Optimize DOM manipulations.
  • Blocking Strategy: Manage script loading.
  • Reflow & Repaint Strategy: Minimize layout thrashing.
  • Async Update Strategy: Batch DOM updates.
CSS Strategy
  • Avoid deeply nested selectors (>3 levels).
  • Don't prefix ID selectors unnecessarily.
  • Prefer class selectors over tag selectors.
  • Avoid universal selectors (*).
  • Leverage inherited properties.
DOM Strategy
  • Cache references to DOM elements.
  • Minimize direct DOM operations.
  • Use DocumentFragment for batch DOM insertions.
Blocking Strategy
  • Use defer for scripts dependent on the DOM/other scripts.
  • Use async for independent scripts.
Reflow & Repaint Strategy

Reflow (layout) and repaint (painting) are expensive. Minimize them by:

  • Reading and writing DOM properties in batches (avoid interleaving).
  • Using CSS classes to apply multiple style changes at once.
  • Taking elements offline (e.g., display: none) before multiple manipulations.
Async Update Strategy

When modifying the DOM within an asynchronous callback, wrap the changes in a microtask (e.g., Promise.resolve().then(...), queueMicrotask()) to batch updates.

Six Key Metrics

These metrics provide measurable goals and additional optimization areas, supplementing the core strategies.

  • Load Optimization: Metrics and techniques for resource loading (e.g., Time to First Byte, First Contentful Paint).
  • Execution Optimization: Improving runtime script efficiency.
  • Rendering Optimization: Metrics related to rendering pipeline (e.g., Largest Contentful Paint, Cumulative Layout Shift).
  • Style Optimization: CSS-specific performence considerations.
  • Script Optimization: JavaScript-specific performance considerations.
  • V8 Engine Optimization: Techniques tailored to the Chrome V8 JavaScript engine's characteristics (e.g., hidden classes, inline caching).

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.