Home Blog How to Make Your Jekyll Site Load Under 1 Second
Tutorial

How to Make Your Jekyll Site Load Under 1 Second

Practical techniques to optimise Jekyll site performance — image compression, CSS/JS minification, lazy loading, caching headers, and Core Web Vitals improvements.

How to Make Your Jekyll Site Load Under 1 Second

Jekyll sites are inherently fast — there’s no PHP, no database, no server-side rendering. But “fast” isn’t automatic. Poor image handling, unminified CSS, and render-blocking scripts can slow even a static site to a crawl. This guide covers practical techniques to get your Jekyll site loading in under a second.


Measure First

Before optimising, measure your current performance so you know what actually needs fixing.

Tools:

  • PageSpeed Insights — Google’s official tool, shows Core Web Vitals scores
  • GTmetrix — detailed waterfall charts showing exactly what’s slow
  • WebPageTest — advanced testing from multiple locations

Run your homepage and a typical post through PageSpeed Insights. The report will highlight your biggest opportunities.


1. Choose a Fast Hosting Platform

The hosting provider has the biggest impact on time-to-first-byte (TTFB).

Platform Free Plan CDN TTFB
Cloudflare Pages Yes Global (300+ PoPs) Excellent
Netlify Yes Global Very good
GitHub Pages Yes Limited Good
Vercel Yes Global Very good

For the fastest possible Jekyll hosting, use Cloudflare Pages. It has the most distributed CDN, meaning files are served from the closest location to each user worldwide.


2. Optimise Images (Biggest Win)

Images are the #1 cause of slow pages. A single unoptimised photo can be 3–5MB — more than all your HTML, CSS, and JS combined.

Convert to WebP

WebP images are 25–35% smaller than JPEG at the same quality.

# Convert a single image
cwebp -q 85 photo.jpg -o photo.webp

# Batch convert all JPEGs
for f in assets/images/*.jpg; do
  cwebp -q 85 "$f" -o "${f%.jpg}.webp"
done

Serve WebP with a JPEG fallback:


<picture>
  <source srcset="{{ image | replace: '.jpg', '.webp' }}" type="image/webp">
  <img src="{{ image }}" alt="{{ alt }}" loading="lazy">
</picture>

Lazy Load Images

Add loading="lazy" to all images that are not in the initial viewport:


<img src="{{ post.image }}" alt="{{ post.title }}" loading="lazy" width="800" height="450">

Always include width and height to prevent Cumulative Layout Shift (CLS).

Responsive Images

Serve appropriately-sized images for each screen size:

<img
  srcset="/assets/images/hero-400.webp 400w,
          /assets/images/hero-800.webp 800w,
          /assets/images/hero-1200.webp 1200w"
  sizes="(max-width: 600px) 400px,
         (max-width: 1000px) 800px,
         1200px"
  src="/assets/images/hero-800.webp"
  alt="Hero image"
  loading="lazy">

The jekyll-picture-tag plugin automates this.


3. Minify CSS and JavaScript

Minify Sass

Jekyll compiles Sass automatically. Set it to compressed output:

# _config.yml
sass:
  style: compressed
  sourcemap: never

This removes whitespace and comments from all CSS. Typically reduces CSS size by 20–30%.

Minify HTML

Add the jekyll-minifier plugin:

gem "jekyll-minifier"
# _config.yml
jekyll-minifier:
  compress_javascript: true
  compress_css: false  # Already handled by Sass
  remove_comments: true
  remove_intertag_spaces: true

Defer JavaScript

Any script that doesn’t need to run before the page renders should be deferred:

<!-- In your layout's </body> or with defer -->
<script src="/assets/js/main.js" defer></script>

Never block rendering with scripts in <head> unless absolutely necessary.


4. Optimise Font Loading

Google Fonts are a common performance culprit. Each font family adds an extra DNS lookup and CSS request.

Self-Host Fonts

Download fonts and serve them from your own server:

  1. Download fonts from Google Fonts Helper
  2. Place in assets/fonts/
  3. Define with @font-face in your CSS
@font-face {
  font-family: 'Inter';
  src: url('/assets/fonts/inter-v13-latin-regular.woff2') format('woff2');
  font-display: swap;
  font-weight: 400;
}

Preload Critical Fonts

<link rel="preload" href="/assets/fonts/inter-regular.woff2" 
      as="font" type="font/woff2" crossorigin>

Use font-display: swap

This renders text in a fallback font while the custom font loads, preventing invisible text (FOIT):

@font-face {
  font-display: swap;  // Always include this
}

5. Reduce Render-Blocking Resources

Inline Critical CSS

For the fastest possible First Contentful Paint, inline the CSS needed to render above-the-fold content:

<!-- In your <head> -->
<style>
  /* Critical CSS — only what's needed to render the visible part of the page */
  body { margin: 0; font-family: sans-serif; }
  .header { background: #fff; padding: 1rem 2rem; }
  /* etc. */
</style>
<!-- Load the rest asynchronously -->
<link rel="stylesheet" href="/assets/css/main.css" media="print" onload="this.media='all'">

Tools like Critical can extract critical CSS automatically.


6. Set Caching Headers

Static files don’t change — tell browsers to cache them aggressively.

On Netlify (netlify.toml):

[[headers]]
  for = "/assets/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
  for = "/*.html"
  [headers.values]
    Cache-Control = "public, max-age=0, must-revalidate"

On Cloudflare Pages, caching is configured automatically — static assets get long cache lifetimes, HTML files are always fresh.


7. Enable Compression

All major hosts (Cloudflare, Netlify, Vercel) compress responses with Brotli or gzip automatically. If self-hosting with Nginx:

gzip on;
gzip_types text/html text/css application/javascript image/svg+xml;
brotli on;
brotli_types text/html text/css application/javascript image/svg+xml;

Compression reduces HTML/CSS/JS transfer size by 60–80%.


Core Web Vitals Targets

Metric Target Common Cause of Failure
LCP (load) < 2.5s Unoptimised hero image
INP (interactivity) < 200ms Heavy JavaScript
CLS (stability) < 0.1 Images without dimensions, late-loading fonts

With a Jekyll static site on Cloudflare Pages, hitting green on all three is achievable with the optimisations above.


Looking for a fast, well-optimised Jekyll theme? All themes on JekyllHub are tested for performance — browse the collection and filter by your use case.


Build time performance: keeping Jekyll fast for large sites

Site load performance is visible to users; build time performance affects your development workflow. As a Jekyll site grows past a few hundred posts, build times can creep into the 30–60 second range, making rapid iteration frustrating.

Several Jekyll configuration settings reduce build time significantly.

Use --incremental for development. The incremental regeneration flag tells Jekyll to only rebuild changed files rather than the entire site on every change:

bundle exec jekyll serve --incremental

Incremental builds are dramatically faster for large sites — a full build that takes 45 seconds might complete incrementally in 2 seconds. The trade-off: templates that depend on data from multiple sources (like a homepage that aggregates posts) may not update correctly when only a post file changes. Run a full build periodically to ensure everything is in sync.

Set limit_posts during development. If you do not need to see all posts while working on a template:

# _config.development.yml
limit_posts: 20

Run with the development config overlay: bundle exec jekyll serve --config _config.yml,_config.development.yml. The full site builds only for production.

Profile your build. Jekyll’s --profile flag outputs a table showing how much time was spent rendering each file:

bundle exec jekyll build --profile

The profile output identifies which templates or plugins are consuming the most build time. A slow _includes/ file called inside a loop of 500 posts is a common culprit — optimising that one include can halve the build time.

Use --strict_front_matter. This flag makes Jekyll fail on invalid front matter rather than silently ignoring it. While not a performance optimisation, it prevents silent failures where missing or malformed front matter causes incorrect output without any error message.

Image optimisation pipeline in CI/CD

Manually optimising images before committing is error-prone — it is easy to forget when writing a new post. A CI/CD pipeline that automatically optimises images on push ensures every image is compressed without manual intervention.

Add an image optimisation step to your GitHub Actions workflow:

- name: Optimise images
  run: |
    npm install -g imagemin-cli imagemin-webp imagemin-mozjpeg imagemin-pngquant
    imagemin assets/images/**/*.{jpg,png} --out-dir=assets/images \
      --plugin=mozjpeg --plugin=pngquant
    imagemin assets/images/**/*.jpg --out-dir=assets/images --plugin=webp

This runs before the Jekyll build step, so the compressed images are what gets built into the site. The first run may take a minute; subsequent runs skip unchanged files.

For a simpler approach, sharp via Node.js handles modern image processing with resizing, WebP conversion, and quality control in a single command:

// scripts/optimise-images.js
const sharp = require('sharp');
const glob = require('glob');

glob('assets/images/**/*.{jpg,png}', (err, files) => {
  files.forEach(file => {
    sharp(file)
      .webp({ quality: 85 })
      .toFile(file.replace(/\.(jpg|png)$/, '.webp'));
    
    sharp(file)
      .jpeg({ quality: 85, progressive: true })
      .toFile(file);
  });
});

Run this as part of your build pipeline and update your templates to use WebP with JPEG fallback.

Checking for performance regressions with Lighthouse CI

Manual Lighthouse checks are fine for initial setup, but they only catch regressions when you remember to run them. Lighthouse CI integrates into GitHub Actions to automatically fail pull requests that reduce performance below your threshold:

# .github/workflows/lighthouse.yml
name: Lighthouse CI

on: [push]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Build Jekyll site
        run: bundle exec jekyll build
      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v12
        with:
          urls: |
            http://localhost:4000/
            http://localhost:4000/blog/
          budgetPath: ./lighthouse-budget.json
          uploadArtifacts: true
          temporaryPublicStorage: true

Define your performance budget in lighthouse-budget.json:

[{
  "path": "/*",
  "timings": [
    {"metric": "interactive", "budget": 3000},
    {"metric": "first-contentful-paint", "budget": 1500}
  ],
  "resourceSizes": [
    {"resourceType": "total", "budget": 500},
    {"resourceType": "image", "budget": 300}
  ]
}]

If a pull request causes Lighthouse scores to drop below the budget, the CI check fails and you see the regression before it reaches production. This is the most reliable way to ensure performance standards are maintained as the site grows.

Third-party scripts and their performance cost

Many Jekyll sites accumulate third-party scripts over time: Google Analytics, Hotjar, Intercom, social share buttons, comment widgets, ad scripts. Each third-party script adds a network request, a DNS lookup, and CPU time for execution. The cumulative effect on Total Blocking Time can be severe.

Audit your third-party scripts annually. For each one, ask: does it justify its performance cost? Is there a lighter alternative? Can it be loaded with a user interaction trigger instead of automatically?

Analytics are a common area for optimisation. Google Analytics 4 with the standard tag is heavier than necessary for most blogs. Alternatives like Plausible (< 1kb) and Fathom (lightweight, privacy-focused) provide visitor counts and traffic sources without the complexity and performance overhead of GA4. If you only need basic traffic numbers, the switch is worth making.

Social share buttons loaded from Twitter and Facebook include their entire widget SDKs — each one is hundreds of kilobytes. Replace them with plain HTML links using pre-built share URLs:


<a href="https://twitter.com/intent/tweet?text={{ page.title | url_encode }}&url={{ page.url | absolute_url | url_encode }}" 
   target="_blank" rel="noopener">Share on Twitter</a>

This achieves the same sharing action with zero JavaScript and no third-party requests.

Performance on a Jekyll static site is achievable at a very high level — 90+ Lighthouse scores across all four categories are realistic with the techniques in this guide. The combination of Jekyll’s pre-rendered HTML, a CDN host like Cloudflare Pages, optimised images, minimal JavaScript, and self-hosted fonts delivers the kind of loading speed that most CMS-based sites cannot match without significant infrastructure investment. Browse Jekyll themes on JekyllHub with performance as a filter criterion — well-built themes already apply most of these optimisations, giving you a strong starting point.

The performance gap between a well-optimised Jekyll site and a poorly-optimised one is not subtle — it is the difference between a 95 Lighthouse score and a 45. The techniques in this guide do not require advanced infrastructure or significant effort to implement; they are mostly configuration choices, image format decisions, and hosting selections that can be made in an afternoon and pay dividends for every visitor to your site indefinitely.

Making performance a habit, not an afterthought

The best time to optimise a Jekyll site is when you first set it up — choosing the right host, picking a performance-conscious theme, and configuring image compression from the start. The second best time is now. Run a Lighthouse audit on your current site, pick the single lowest-scoring category, and fix that first. One area at a time, each improvement compounds with the others. A site that earns a 95 across all four Lighthouse categories will rank higher in search, load faster on mobile networks, and keep visitors engaged longer — outcomes that directly benefit every goal a Jekyll site serves, from portfolio exposure to blog readership to product sales.

Run Lighthouse from Chrome DevTools (Cmd+Shift+I → Lighthouse tab) rather than PageSpeed Insights when testing locally — it gives you immediate feedback on each iteration without deploying. Reserve PageSpeed Insights for testing your production URL, where CDN caching, HTTPS, and HTTP/2 multiplexing are all active and give you the true real-world score your visitors experience.

Share LinkedIn