Loading...

Loading...

Performance Optimization

This page explains the blog’s performance optimization measures: image lazy loading, asset minification, CDN acceleration, image format optimization, and LCP (largest contentful paint) optimization principles. Most optimizations work out of the box — the key is understanding the layered strategy and not breaking it when adding resources.

1. Lazy Loading (lazyload)

Purpose: images and comment plugins outside the visible area aren’t loaded in advance, reducing first-paint requests and traffic. Config lives in the theme config (edit userConfig/_config.tmp.yml for production):

lazyload:
  enable: true          # true | false
  loading_img: /medias_webp/loading2.svg       # loading placeholder (light)
  loading_img_dark: /medias_webp/loading2-dark.svg  # placeholder (dark)
  onlypost: false       # true = lazy loading only on post pages
  offset_factor: 2      # trigger offset multiplier (base = viewport height)
  • Add the no-lazy class to skip lazy loading (e.g. first-screen banner)
  • With onlypost: false it applies site-wide; standalone pages can opt in with lazyload: true in front-matter
  • Tune offset_factor by deployment request speed: larger = loads earlier (lazy loading starts closer to the bottom)

2. Asset Minification (hexo-minify)

Purpose: minify JS/CSS/HTML at build time to reduce size. Root config _config.yml:

minify:
  preview: false            ## whether to minify during local preview (false = minify in production)
  exclude: ['*.min.*']      # skip already-minified files
  js:   { enable: true }    # JS minification
  css:  { enable: true }    # CSS minification
  html:
    enable: true
    options:
      minifyJS: true        # minify inline JS in HTML
      minifyCSS: true       # minify inline CSS in HTML
      removeComments: true  # strip comments
      collapseWhitespace: true # strip extra whitespace
      removeAttributeQuotes: true
  image:
    enable: false           # image minification off by default (images have their own optimization, see section 4)

Notes:

  • preview: false means local hexo server doesn’t minify; verify minification with npm run build (see the Deployment chapter)
  • Compression is toggled via # minify-switch marker lines in the root _config.yml; tools/cicd.sh sed-switches them per build mode (-d off, -r/-c/-t on), then restores them automatically after the build

3. CDN Acceleration

Purpose: static assets are served from jsDelivr’s global nodes, fetched from the nearest edge. The mechanism is placeholder injection: templates contain {cdnPathVersion}, replaced with real CDN URLs at CI build time (see the CDN section of the Global Configuration chapter).

# theme config
jsDelivr:
  url: {cdnPathVersion}      # versioned CDN path (replaced at build time)
  latesturl: #{cdnPathLatest} # latest path (commented out, not enabled)
cdn:
  url: "{cdnUrl}"            # JS/CSS CDN URL
  mediaUrl: "{mediaUrl}"     # image/media CDN URL
  resUrl: "{resUrl}"         # resource CDN URL
  • The version number is the key: -r releases bump tools/.VERSION and inject {urlVersion} (e.g. ?v={urlVersion}) so CDN and browser caches fetch new files; content-only -c doesn’t bump — CDN hits the old version, which is fine since content is unchanged
  • After release tools/purge-cdn.sh -a flushes CDN cache (auto-called in cicd.sh release mode)
  • Images use a separate CDN domain ({mediaUrl}), separated from JS/CSS for independent cache control

4. Image Optimization (webp)

Purpose: images are the biggest chunk of this blog’s size. Site images are uniformly webp, far smaller than jpg/png, and distributed via CDN. webp files live in the theme source themes/matery/source/medias_webp/ (copied to public/medias_webp/ at build time; URLs use /medias_webp/...):

# example: the featureimages library is all webp
/medias_webp/featureimages/0.webp
/medias_webp/avatar.webp
/medias_webp/loading2.svg       # SVG placeholder (vector, tiny)
  • Convert new images to webp before committing them: the image repo is the submodule userConfig/cloudimg/ (synced by tools/processRepo.sh); imgurl.sh processes URLs at build time
  • Image loading follows section 1 lazy loading + section 3 CDN distribution, both layers active
  • hexo-minify’s image.enable is off by default — in-site webp is already the final form, no second compression needed

5. LCP Optimization Principles (JS Layering)

Purpose: LCP (Largest Contentful Paint) is the key first-screen metric. A JS loading strategy layering refactor was completed on 2026-08-11; the principle is first-screen essentials synchronous, the rest defer, heavy items on demand:

LayerJSLoadingRationale
L0 critical syncutils + colorSchemasynchronous (in head)global namespace dependencies, needed for first screen
L1 defermaterialize/masonry/imagesloaded/aos/events/plugins/lightgallery/search enginesdeferparallel download without blocking parsing; defer preserves execution order
L2 on-demand dynamictyped/mermaid/mathjax/echarts/tocbotconditional dynamic injectionzero cost on first screen
L3 asyncumami/busuanzi etc. analyticsasyncnever blocks rendering

Three disciplines when adding your own JS:

  1. defer by default: <script src="..." defer></script>, never block synchronously
  2. Don’t touch the two L0 files: utils/colorSchema are the global namespace foundation; sync loading is a design decision
  3. Heavy libraries load on demand: mermaid/mathjax/echarts only appear on specific pages and shouldn’t be in the site-wide scripts — inject them on conditional trigger

Verification:

# build output check: script tags generated by scripts.ejs should carry defer
grep -rl defer public/js/  # or inspect the script tags in public/index.html

Measured data shows LCP dropped from 4.2s to 1.8s (57% reduction) after optimization.

5.1 jQuery Removal & Script Layering Details (P4/P5 site-wide complete 2026-09-05)

The theme has removed jQuery site-wide, switching to native DOM APIs (querySelector / addEventListener / classList / getComputedStyle etc.). Current status of jQuery-related items in themes/matery/_config.yml libs.js.* (sources: themes/matery/_config.yml, themes/matery/layout/_partial/common/scripts.ejs):

Config itemStatusReason
libs.js.jqueryDisabled (commented out)Comment: “2026-09-05 disabled: P4/P5 site-wide jQuery removal complete”
libs.js.jqueryUIConfig key retained (_config.yml not commented), but the <script> in scripts.ejs is commented out → not actually loadedkey still present in _config.yml
libs.js.jqueryBarragerDisabled (commented out)Disabled after contact page removal (jQuery barrage deprecated)
libs.js.jqueryPjaxConfig key retainedNo references remain in layout/, scripts/, or source/js/

As a result, the L0 layer in the §5 table no longer includes jquery — the synchronous layer contains only utils.js + color-schema.js.

Critical inline scripts must precede defer (sources: head.ejs, scripts.ejs):

  • head.ejs synchronously loads utils + colorSchema and executes export_config() (injects window.CONFIG) — these must come before all defer scripts so they can read the global namespace/config.
  • scripts.ejs multilingual config MATERY_LANG and i18n copy MATERY_I18N are synchronous inline <script> tags, deliberately placed before wiki.js (defer); the source comment reads “injected as synchronous script, guaranteed to execute before all defer scripts”.

Loading method classification:

  • defer: materialize / masonry / imagesloaded / aos / events / plugins / search / scrollProgress / lightgallery / boot; instantpage is type="module" (module already implies defer; no defer attribute added).
  • async: umami / busuanzi / tagCommon (analytics and comment helpers, non-render-blocking).
  • Conditional (specific pages/layouts only): typed / mermaid / mathjax / echarts / tocbot.

JS compatibility pitfall (2026-09-22): classList.add('a b') with a space-separated multi-class string throws InvalidCharacterError (DOMTokenList rejects spaces) — use classList.add('a', 'b') or split and add individually. The homepage once failed this way.

6. Page Preloading (instantpage)

Purpose: preload the target page when the user hovers a link, making clicks feel instant.

Config (theme config):

instantpage:
  enable: true

Notes: based on instant.page; enable: true applies site-wide; on mobile (no hover) it degrades gracefully without preloading.

7. Dark Feature Images (darkFeatureImages)

Purpose: feature image library for dark mode — the dark counterpart of featureimages, used for random picks in dark mode.

Config (theme config):

darkFeatureImages:
  - /medias_webp/featureimages/dark/0.webp
  - /medias_webp/featureimages/dark/1.webp
  - /medias_webp/featureimages/dark/2.webp
  # ... more dark images

Notes: corresponds to the light featureimages library; in dark mode, banners/covers pick randomly from this list; images live in themes/matery/source/medias_webp/featureimages/dark/.

8. Image Size Plugin (image_size_plugin)

Purpose: automatically add width/height attributes to images at build time to prevent layout shift (CLS); supports mapping local images to online CDN paths.

Config (theme config):

image_size_plugin:
  enable: true
  proxy_url:        # image proxy address (e.g. http://xray:10089)
  debug: true       # true | false print switch
  onlypost: false   # true = only process post images
  # local image paths (array: theme assets + site post images)
  local_path:
    - themes/matery/source
    - source
  # local path index for mapping to online paths
  map_paths:
    - userConfig/cloudmedia
    - userConfig/cloudimg
    - themes/matery/source
  # local-to-online path mapping
  map_paths_web_root:
    userConfig/cloudmedia: https://cdn.17lai.site
    userConfig/cloudimg: https://cimg1.17lai.site

Notes: skips images with the no-size class or existing width/height attributes; map_paths + map_paths_web_root replace local image paths with online CDN URLs (images in the submodule repos userConfig/cloudimg and userConfig/cloudmedia go through this mapping).

9. CSS Async + SEO (2026-09-07)

9.1 Non-critical CSS Async Loading

Animation libraries (aos/animate.css) are not critical above the fold. Load them async with media="print" onload="this.media='all'" to eliminate render-blocking:

<link rel="stylesheet" href="..." media="print" onload="this.media='all'">
<noscript><link rel="stylesheet" href="..."></noscript>

Note: When rel="stylesheet preload" and a regular stylesheet point to the same URL, browsers auto-deduplicate (no double download) — no need to remove either.

9.2 SEO Structured Data (JSON-LD)

The theme ships layout/_partial/common/structured-data.ejs, auto-injected into every page:

  • WebSite (all pages): site name + SearchAction (sitelinks searchbox rich result)
  • BlogPosting (post pages): headline/description/date/author/cover/keywords/category
  • Person + Organization: cross-referenced via @id (avoids duplicate data)

All data comes from existing variables (config.title/url/author/language + page.*) — no manual maintenance. Validate with Google Rich Results Test (after production deploy).

9.3 Social Share Image (og:image)

# Theme config (must also sync userConfig/_config.tmp.yml)
open_graph:
  default_og_image: /medias_webp/featureimages/15.webp

Priority: page.img (post image) → theme.default_og_image (default) → theme.featureImages[0]. Post pages use the post cover; home/list pages use the default image.

9.4 LCP Element Priority

Above-the-fold images like the logo carry fetchpriority="high" (built into header/mobile-nav). Note: the home banner background is JS-injected (random image rotation) — do NOT change it to static CSS, that would break the random rotation feature.

Appendix: Performance Optimization Quick Reference

MeasureConfig locationKey points
Lazy loadingtheme config lazyloadno-lazy class skips; onlypost controls scope
Asset minificationroot _config.yml minifylocal preview not minified; -d disables
CDNtheme config jsDelivr/cdn-r bumps the version to flush caches
Images/medias_webp/ + CDNuniform webp; image repo is a submodule
LCP layeringlayout/_partial/common/scripts.ejsL0 sync / L1 defer / L2 on-demand / L3 async
CSS asynclayout/_partial/common/css.ejsaos/animate via media="print" onload
SEO structuredlayout/_partial/common/structured-data.ejsauto JSON-LD WebSite + BlogPosting
Social share imagetheme config default_og_imagefallback default image for home/list (also sync userConfig/_config.tmp.yml)
LCP elementheader/mobile-nav logo imgfetchpriority="high"
Comments
Data Loading...