Speed is not a luxury for your Ghost CMS website. It is the foundation everything else rests on. Your content quality, your SEO rankings, your subscriber conversion rates, your reader retention, all of it degrades when your site is slow. And while Ghost already has a significant performance advantage over heavier platforms out of the box, that advantage only holds if you build on it deliberately.
This guide is about doing exactly that. We are going to go through every meaningful lever you can pull to make your Ghost site load faster, score higher on Core Web Vitals, and deliver a genuinely snappy experience to readers on any device or connection speed.
Some of what is here is specific to Ghost. Some of it is broader web performance work that applies to any site, but with Ghost-specific context. All of it is grounded in practical implementation rather than surface-level advice.
1. Why Web Performance Matters More Than You Think
Most people understand performance matters. Fewer people understand just how directly and aggressively slow load times affect the numbers that actually count.
The SEO connection
Google has made it clear that page experience signals, including Core Web Vitals, are ranking factors. A site that passes Core Web Vitals thresholds does not automatically outrank a site with better content. But when content quality and authority are comparable, the faster site wins. For Ghost publishers competing for search traffic in established niches, that margin matters.
Google also uses real-world field data collected from Chrome users to evaluate your site's performance. This is different from lab data you collect with Lighthouse. Your visitors' actual experience on their actual devices and connections is what feeds into Google's ranking signals. You cannot game this with a high score on a single test from a fast machine.
The reader retention connection
Studies from Google and various large-scale web performance analyses have consistently shown that as page load time increases, bounce rates climb sharply. Users who arrive and wait more than a couple of seconds are increasingly likely to leave before your content even renders. For a publication or newsletter where reader retention and subscriber growth are the primary metrics, this is not a small issue.
The conversion connection
If you are using Ghost's membership features to build a subscriber base or paid publication, performance affects your conversion rate directly. A slow, janky signup flow loses subscribers at every friction point. Portal, Ghost's membership interface, needs to load quickly and smoothly or readers will abandon before completing signup.
Why Ghost sites specifically need this attention
Ghost has a fast foundation, but it does not write your theme for you. A poorly built Ghost theme with unoptimized images, render-blocking scripts, and bloated CSS can score just as badly as a slow WordPress site. The platform gives you the tools. This guide helps you use them.
2. Understanding Core Web Vitals in the Context of Ghost
Core Web Vitals are Google's set of specific metrics that measure real-world user experience. As of the current specification, there are three:
Largest Contentful Paint (LCP)
LCP measures how long it takes for the largest visible content element in the viewport to render. For most Ghost sites, this is almost always your post's feature image or the hero section on the homepage. A good LCP score is under 2.5 seconds.
The most common causes of poor LCP on Ghost sites:
- A large, unoptimized feature image that has to download fully before it renders
- A render-blocking stylesheet or script that delays the entire page paint
- Slow server response times from an underpowered hosting environment
- A third-party font that blocks text rendering
Cumulative Layout Shift (CLS)
CLS measures visual stability. It captures how much the page layout shifts after initial render. A low CLS score means elements do not jump around as the page loads. A good CLS score is under 0.1.
Common causes of CLS on Ghost sites:
- Images without explicit
widthandheightattributes, which causes the browser to not reserve space for them before they load - Web fonts that cause a flash of invisible text (FOIT) or flash of unstyled text (FOUT), shifting surrounding content when they swap in
- Dynamically injected content above the fold, for example a cookie consent banner or newsletter CTA that pushes content down
Interaction to Next Paint (INP)
INP replaced First Input Delay (FID) as a Core Web Vitals metric. It measures how quickly your page responds to user interactions across the entire session, not just the first one. A good INP score is under 200 milliseconds.
For most content-focused Ghost sites, INP is usually the easiest metric to pass because there are not many interactive elements. The primary risks are heavy JavaScript execution and third-party scripts that hog the main thread.
3. Ghost's Built-in Performance Advantages
Before getting into optimization work, it is worth understanding what Ghost already does for you, because it is quite a bit.
Ghost is Node.js, not PHP
Ghost runs on Node.js, which handles concurrent requests differently than PHP-based platforms. Node.js uses a non-blocking event loop, which means it can handle many simultaneous requests without spinning up a new process for each one. Under real-world load, this translates to faster response times and better resource efficiency compared to traditional PHP/MySQL stacks.
Built-in image processing
When you upload an image to Ghost, the platform automatically creates multiple resized versions. These are used when you output images through the {{img_url}} helper with size parameters. This built-in image processing pipeline means you do not need a separate service or plugin to serve appropriately sized images. It is there by default, but you have to use the helper correctly in your theme to take advantage of it.
Clean HTML output
Ghost's Handlebars templating outputs clean, semantic HTML without the div soup that many CMS platforms generate. Less HTML means smaller documents and faster parsing. This is a real advantage, though a theme developer can easily squander it with careless markup.
No plugin overhead
WordPress performance is often significantly dragged down by plugins that add database queries, enqueue scripts, and inject markup on every page load. Ghost has no plugin system. Every feature that exists on a Ghost site either comes from Ghost itself or from code you deliberately wrote. This means the baseline is clean.
Native RSS, sitemap, and SEO features
Ghost generates sitemaps, RSS feeds, structured data (JSON-LD), and canonical URLs automatically. You do not need to add plugins or scripts for these. They work out of the box without any performance cost from your side.
4. Measuring Performance: Tools and What to Look For
You cannot improve what you do not measure. Here are the tools you should be using regularly.
Google PageSpeed Insights
PageSpeed Insights runs both a lab test using Lighthouse and shows field data from the Chrome User Experience Report (CrUX) for your URL if enough data exists. Always check both. Lab data tells you what you can do. Field data tells you what your actual users are experiencing.
Run PageSpeed Insights against your homepage, a typical blog post, and your tag archive pages. Each of these has different performance characteristics.
Lighthouse in Chrome DevTools
Open Chrome DevTools (F12 or Cmd+Option+I), go to the Lighthouse tab, and run a performance audit. This is useful during active development because you can run it immediately after making changes without pushing to a live server. Run it in Incognito mode to avoid extensions skewing the results.
WebPageTest
WebPageTest is the most powerful free performance testing tool available. It lets you test from real browsers in specific locations, simulate different connection speeds, and run filmstrip and waterfall views. The waterfall view shows every resource your page loads in the order it loads, which makes it invaluable for diagnosing specific bottlenecks.
Pay attention to:
- The time to first byte (TTFB) - anything over 600ms suggests a server or caching problem
- The waterfall shape - a wide, staircase-shaped waterfall means resources are loading serially when they should load in parallel
- Resources that load late and block rendering
Google Search Console
In Search Console, the Core Web Vitals report shows your field data aggregated across the URLs on your site, grouped as Good, Needs Improvement, or Poor. This is the most important data source for understanding your site's real-world performance because it reflects what Google's ranking signals actually see.
Chrome User Experience Report (CrUX)
CrUX is the underlying dataset PageSpeed Insights and Search Console pull from. You can query it directly through the CrUX API or explore it using tools like crux.run. This is most useful for monitoring trends over time.
5. Image Optimization for Ghost Websites
Images are almost always the single largest contributor to page weight on a Ghost site. Optimizing them consistently delivers the biggest performance gains.
Use Ghost's built-in image sizes
Ghost automatically generates multiple sizes when you upload images. In your theme, use the {{img_url}} helper with explicit size parameters and a proper srcset attribute to let the browser download only the size it needs:
{{#post}}
{{#if feature_image}}
<figure class="post-feature-image">
<img
src="{{img_url feature_image size="l"}}"
srcset="
{{img_url feature_image size="s"}} 400w,
{{img_url feature_image size="m"}} 750w,
{{img_url feature_image size="l"}} 1000w,
{{img_url feature_image size="xl"}} 2000w
"
sizes="(max-width: 800px) 100vw, 800px"
alt="{{title}}"
width="1200"
height="630"
loading="{{#if @first}}eager{{else}}lazy{{/if}}"
>
</figure>
{{/if}}
{{/post}}
A few important things about this example. The sizes attribute tells the browser how wide the image will actually display, so it can calculate which srcset entry to download. Without it, the browser assumes the image is full-viewport width and often downloads a larger file than needed. The width and height attributes are crucial for CLS, as they let the browser reserve the correct space before the image loads. And the loading attribute is set to eager for the first image (which is likely the LCP element) and lazy for everything else.
Define your image sizes in package.json
You control the sizes Ghost generates by defining them in your theme's package.json:
{
"config": {
"image_sizes": {
"xxs": { "width": 30 },
"xs": { "width": 100 },
"s": { "width": 300 },
"m": { "width": 600 },
"l": { "width": 1000 },
"xl": { "width": 2000 }
}
}
}
Ghost generates these sizes when images are uploaded. Existing images are not retroactively resized, so if you change these settings on an existing site, previously uploaded images will only gain the new sizes the next time they are re-uploaded or processed.
Preload your LCP image
For the LCP image, add a preload hint in the <head> section of default.hbs or, if you can output it conditionally, in post.hbs:
{{#post}}
{{#if feature_image}}
<link
rel="preload"
as="image"
href="{{img_url feature_image size="l"}}"
imagesrcset="
{{img_url feature_image size="s"}} 400w,
{{img_url feature_image size="l"}} 1000w
"
imagesizes="(max-width: 800px) 100vw, 800px"
>
{{/if}}
{{/post}}
This tells the browser to start downloading the feature image as early as possible, before the HTML parser even reaches the <img> tag in the body. It is one of the highest-impact single changes you can make for LCP on image-heavy Ghost posts.
Use modern image formats
Ghost supports WebP and AVIF uploads and serves them natively. AVIF provides the best compression ratios (typically 50% smaller than JPEG at the same quality), while WebP is more broadly supported. If you are on Ghost Pro or a recent self-hosted Ghost version, WebP conversion happens automatically for uploaded images.
For images you reference outside of Ghost's upload system (for example, images in your theme's assets/images/ folder), convert them to WebP manually before including them.
Optimize Koenig gallery images
Ghost's gallery card outputs multiple images in a grid. These are often loaded all at once, even images below the fold. Make sure your theme's CSS does not prevent native lazy loading on gallery images and that each image in the gallery HTML has proper loading="lazy" applied. Ghost's default gallery output does include this, but if you are writing custom gallery styles, double-check.
6. CSS Optimization in Ghost Themes
CSS is a render-blocking resource by default. The browser has to download and parse your stylesheet before it can paint anything to the screen. Keeping your CSS lean and loading it efficiently is one of the most direct paths to a better LCP score.
Write minimal, purposeful CSS
The most effective CSS optimization is simply not writing CSS you do not need. Before reaching for a CSS framework, ask whether you genuinely need it. A content-focused Ghost theme does not need a utility framework with hundreds of classes. A clean, well-organized custom stylesheet of a few hundred lines often outperforms a framework that ships dozens of kilobytes of utilities.
Critical CSS inlining
Critical CSS is the subset of your stylesheet that is required to render the above-the-fold content. Inlining it directly in the <head> of your HTML means the browser can paint the initial view without waiting for your external stylesheet to download.
The workflow looks like this:
- Use a tool like critical or PurgeCSS combined with your build process to extract critical CSS
- Inline that CSS in a
<style>tag in your<head> - Load the full stylesheet asynchronously using
rel="preload"with anonloadtrick:
<style>
/* Inlined critical CSS here */
</style>
<link rel="preload" href="{{asset "css/screen.css"}}" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="{{asset "css/screen.css"}}"></noscript>
This is more complex to set up but delivers measurable LCP improvements, often shaving several hundred milliseconds off the initial render.
Purge unused CSS
If you are using a CSS framework like Tailwind or Bootstrap, you are almost certainly shipping CSS classes that are never used in your templates. Tools like PurgeCSS scan your Handlebars templates and JavaScript files and remove any CSS that does not match classes found there.
Set up PurgeCSS as part of your build process. If you are on Tailwind, this is built into Tailwind's configuration with the content option.
Avoid CSS @import
Using @import inside CSS files causes the browser to fetch stylesheets serially. Each imported file is another round-trip request before the full stylesheet is assembled. Instead, use your build tool (Sass, PostCSS, Gulp) to concatenate all your CSS into a single file at build time.
Use CSS for animations, not JavaScript
Animations driven by CSS transform and opacity properties are handled by the browser's compositor thread and do not block the main thread. JavaScript-driven animations block the main thread and can cause INP issues. If you are adding visual transitions or micro-interactions to your Ghost theme, default to CSS keyframe animations and transitions.
7. JavaScript Performance in Ghost Themes
JavaScript is expensive. Every script your theme loads takes time to download, parse, compile, and execute. On mobile devices, this cost is amplified significantly.
Audit what you actually need
Go through every script your Ghost theme loads and ask honestly: is this necessary? A blog or publication typically needs very little JavaScript. You might need:
- A search interface script
- A syntax highlighter for code blocks (if the blog covers technical content)
- A lightbox for image galleries
- Member authentication (handled by Ghost itself via Portal)
You almost certainly do not need:
- jQuery (use vanilla JS)
- A full UI component library
- Parallax scrolling effects
- Animated number counters
- Any tracking pixel beyond one analytics solution
Defer and async non-critical scripts
For any script that is not required for the initial render, add defer or async:
<!-- defer: downloads in parallel, executes after HTML is parsed, in order -->
<script src="{{asset "js/search.js"}}" defer></script>
<!-- async: downloads in parallel, executes as soon as it is ready, out of order -->
<script src="{{asset "js/analytics.js"}}" async></script>
Use defer for scripts that depend on the DOM or on other scripts. Use async for completely independent scripts like analytics that do not interact with your page content.
Load syntax highlighters on demand
If your Ghost site publishes technical articles with code blocks, a syntax highlighting library like Prism.js or Highlight.js is often necessary. But loading it on every page, including non-technical posts and your homepage, is wasteful.
You can conditionally load it only on posts that contain code blocks:
{{#post}}
{{#has tag="code"}}
<script src="{{asset "js/prism.js"}}" defer></script>
{{/has}}
{{/post}}
Or, more reliably, use JavaScript to detect whether any <code> blocks exist on the page before loading the library:
if (document.querySelector('pre code')) {
const script = document.createElement('script');
script.src = '/assets/js/prism.js';
document.body.appendChild(script);
}
Avoid main thread blocking during page load
Long JavaScript tasks (tasks that take more than 50ms on the main thread) block the browser from responding to user input. This directly affects INP. Use the browser's Performance tab in DevTools to identify long tasks. Common causes include:
- Large JavaScript bundles that parse and execute all at once
- Synchronous AJAX calls at page load
- Heavy DOM manipulation immediately on page load
- Third-party scripts that run synchronously
If you find long tasks in your own code, consider breaking work into smaller chunks using requestIdleCallback or setTimeoutwith a delay of 0 to yield control back to the browser between tasks.
8. Font Loading Strategies for Ghost
Web fonts are one of the most common causes of both LCP delay and CLS on Ghost sites. They are also one of the most controllable once you know the right approach.
Self-host your fonts
Loading fonts from Google Fonts or other external CDNs adds DNS lookup time, TLS handshake time, and an additional HTTP request before the font can load. Self-hosting fonts in your Ghost theme's assets/fonts/ folder eliminates these extra round trips.
To self-host a Google Font, use google-webfonts-helper to download the font files and generate the @font-face declarations. Include both WOFF2 (modern browsers) and WOFF (older browsers) formats.
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('/assets/fonts/inter-v13-latin-regular.woff2') format('woff2'),
url('/assets/fonts/inter-v13-latin-regular.woff') format('woff');
}
Use font-display: swap
The font-display: swap descriptor tells the browser to render text using a fallback font immediately and swap to your web font once it downloads. This prevents the Flash of Invisible Text (FOIT) that causes text to be invisible during font loading, which is terrible for both user experience and LCP.
The tradeoff is a Flash of Unstyled Text (FOUT) where readers briefly see fallback font text before it swaps. Minimize this by choosing a fallback font that closely matches your web font's metrics.
Use the size-adjust CSS descriptor
Modern CSS allows you to adjust fallback font metrics to minimize layout shift when your web font loads. This is called a font metric override:
@font-face {
font-family: 'Inter-Fallback';
src: local('Arial');
size-adjust: 107%;
ascent-override: 90%;
descent-override: normal;
line-gap-override: normal;
}
body {
font-family: 'Inter', 'Inter-Fallback', sans-serif;
}
The exact values need to be calibrated for your specific font pairing, but this technique can reduce CLS from font swapping to nearly zero.
Preload critical font files
Add a preload hint for your most important font file (typically the regular weight of your body font):
<link
rel="preload"
href="{{asset "fonts/inter-regular.woff2"}}"
as="font"
type="font/woff2"
crossorigin
>
The crossorigin attribute is required even for same-origin font requests. Without it, the browser downloads the font twice.
Load fewer font weights
Each font weight is a separate file download. Many designs load four, five, or six font weights and never use half of them. Audit your CSS and identify which weights you actually apply. Cutting from four weights to two saves a meaningful amount of bandwidth, especially on mobile connections.
Consider variable fonts
A variable font is a single file that contains the entire range of weights and styles across one continuous design space. Instead of separate files for Regular (400), Medium (500), SemiBold (600), and Bold (700), you load one variable font file that covers all of them. Variable fonts are typically larger than a single static weight but smaller than multiple static weights combined, while giving you access to any weight in between.
9. Ghost Hosting and Server-Side Performance
No amount of front-end optimization compensates for a slow server. Your Time to First Byte (TTFB) is the foundation of everything else. If your server takes 1.5 seconds to respond, your LCP cannot possibly be under 2.5 seconds.
Ghost Pro vs self-hosted
Ghost Pro, Ghost's managed hosting service, is the easiest path to good server performance. The infrastructure is managed by the Ghost team, includes a CDN, and is tuned for Ghost specifically. It is the right choice for publishers who do not want to manage server infrastructure.
Self-hosted Ghost gives you more control and can be cheaper at scale, but you are responsible for server performance. The choices you make here matter a lot.
Choosing a server for self-hosted Ghost
For self-hosted Ghost, a few principles:
Use a server close to your audience. Server location has a direct impact on TTFB for readers far from the server. If most of your readers are in Europe and your server is in the US, they pay a latency penalty on every request that a CDN can only partially mitigate.
Use a server with adequate resources. Ghost is not as resource-heavy as some platforms, but a $5/month shared hosting plan with one CPU and 512MB of RAM will produce inconsistent TTFB under any meaningful traffic. A $20-40/month VPS with 2 CPUs and 2-4GB of RAM is a much more reliable baseline.
Popular hosting choices for self-hosted Ghost:
- DigitalOcean Droplets (Ghost has an official one-click install)
- Vultr
- Linode/Akamai Cloud
- Hetzner (excellent price-to-performance, especially for European audiences)
- Railway and Render for smaller, lower-traffic sites
TTFB targets
A good TTFB from the server is under 200ms. Under 100ms is excellent. If your TTFB is above 600ms, something is wrong at the server or network level and front-end optimization will have limited impact until you address it.
Enable HTTP/2 or HTTP/3
HTTP/2 allows multiple requests to travel over a single connection simultaneously, eliminating the head-of-line blocking that HTTP/1.1 suffers from. HTTP/3 improves on this further with the QUIC protocol, which is especially beneficial on mobile networks with packet loss.
Most modern hosting environments enable HTTP/2 by default. If you are managing your own Nginx configuration, make sure listen 443 ssl http2; is set in your server block. HTTP/3 requires more configuration and is not yet universally supported but is worth enabling if your hosting environment supports it.
Nginx configuration tuning
If you are running self-hosted Ghost with Nginx as a reverse proxy, a few configuration optimizations help:
Enable gzip compression:
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
Enable Brotli compression (requires the ngx_brotli module, but provides better compression than gzip):
brotli on;
brotli_comp_level 6;
brotli_types text/plain text/css text/xml application/json application/javascript application/rss+xml image/svg+xml;
Set long cache headers for static assets:
location ~* \.(css|js|png|jpg|jpeg|gif|ico|woff|woff2|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
10. Caching Strategies for Ghost CMS
Caching is one of the highest-leverage performance improvements for any Ghost site. A cached response is served in milliseconds. An uncached response requires Ghost to query the database, render the template, and return HTML. Even on a fast server, the difference is significant.
Ghost's built-in caching
Ghost has an internal in-memory cache that stores rendered HTML for pages. This cache is populated on the first request after it expires and serves subsequent requests from memory. By default, Ghost's cache timeout is short (a few minutes), which means content updates appear quickly but cache efficiency is low.
For production sites, you want a caching layer in front of Ghost, not just Ghost's internal cache.
Nginx-level caching
With self-hosted Ghost, you can configure Nginx to cache the full HTML response for your public pages:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=ghost_cache:10m max_size=1g inactive=60m use_temp_path=off;
server {
location / {
proxy_cache ghost_cache;
proxy_cache_valid 200 301 302 10m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_lock on;
proxy_pass http://localhost:2368;
# Do not cache authenticated requests or admin
proxy_no_cache $cookie_ghost-members-ssr;
proxy_cache_bypass $cookie_ghost-members-ssr;
}
}
This caches HTML responses for 10 minutes. Public pages (including your homepage and blog posts) are served from cache. Pages with a Ghost member session cookie bypass the cache, ensuring logged-in members see personalized content.
Cache invalidation
One of the downsides of aggressive caching is that content updates do not appear immediately. Ghost sends cache purge webhooks when content is published or updated, which you can use to invalidate cached pages. If you are using a CDN like Cloudflare, you can configure its cache purge API to be called via Ghost's webhook system.
Redis caching for Ghost
For high-traffic self-hosted Ghost installations, adding Redis as a caching layer in front of Ghost's database queries can reduce database load significantly. This is an advanced configuration but worth considering if your site receives significant concurrent traffic.
11. Content Delivery Networks (CDN) for Ghost
A CDN distributes your site's content across servers in multiple geographic locations and serves each visitor from the location closest to them. This reduces latency for visitors who are geographically distant from your origin server.
What a CDN caches for Ghost
A CDN typically caches:
- Static assets (CSS, JavaScript, images, fonts from your theme's
assets/folder) - Ghost's uploaded image files
- HTML responses for public pages (if configured to do so)
A CDN does not cache (and should not):
- Ghost admin pages
- Member authentication endpoints
- Portal API calls
- Any request with a Ghost member session cookie
Cloudflare for Ghost
Cloudflare is the most commonly used CDN for Ghost sites. The free tier covers most use cases and includes global CDN, DDoS protection, and automatic HTTP/2.
Key Cloudflare settings for Ghost:
Turn on Auto Minify for JavaScript, CSS, and HTML under Speed > Optimization. Cloudflare minifies these resources before caching them.
Enable Brotli compression under Speed > Optimization.
Set up a Page Rule or Cache Rule to bypass the cache for Ghost admin URLs:
ghost.io/ghost/* Cache Level: Bypass
yoursite.com/ghost/* Cache Level: Bypass
Also bypass cache for URLs with the Ghost member session cookie.
Configure Browser Cache TTL to respect Ghost's existing cache headers, or set a long TTL for static assets.
Ghost Pro and built-in CDN
If you are on Ghost Pro, the platform includes a CDN for your media and static assets. Your theme's assets and uploaded images are automatically served from CDN edge locations. You still benefit from adding Cloudflare in front of Ghost Pro for HTML caching and additional edge performance.
Cloudflare's image optimization
Cloudflare Polish (available on paid plans) automatically converts uploaded images to WebP format and strips unnecessary metadata, reducing image file sizes by 20-50% without additional work on your part. Combined with Ghost's built-in responsive image generation, this can significantly reduce your image-related page weight.
12. Ghost Theme Code Optimization Tips
Beyond assets and server configuration, your theme's code itself has a meaningful impact on performance. Here are the patterns that make the biggest difference.
Keep your DOM size manageable
The browser has to parse and build a DOM tree from your HTML. Large DOM trees (typically defined as more than 1,500 nodes) consume more memory, produce longer style calculations, and cause more expensive layout reflows. Audit your theme's HTML output and eliminate unnecessary wrapper divs, redundant containers, and decorative elements that could be handled with CSS instead.
Avoid layout thrashing in JavaScript
Layout thrashing happens when JavaScript code reads and then writes to the DOM in an alternating pattern, forcing the browser to recalculate layout repeatedly. If your theme has any JavaScript that reads layout properties (like offsetWidth, clientHeight, getBoundingClientRect) and then modifies styles, it may be triggering forced synchronous layouts.
Batch your DOM reads before your DOM writes. Libraries like FastDOM can help structure this correctly.
Use will-change sparingly
The will-change CSS property hints to the browser that an element is going to change, so it should be promoted to its own compositor layer in advance. This can help animation performance, but overusing it causes excessive memory usage. Only apply it to elements that actually animate, and remove it when the animation is not active.
Optimize your navigation markup
Navigation is rendered on every page. A complex navigation structure with lots of nested elements, dropdown menus, and JavaScript event listeners adds cost on every page load. Keep navigation markup simple. If you have a mega menu or complex dropdown, load its JavaScript lazily (on hover or on first interaction) rather than on page load.
Use content-visibility: auto for off-screen sections
The content-visibility: auto CSS property tells the browser to skip rendering work for elements that are currently off-screen. On long pages (like a homepage with many post cards), this can significantly reduce rendering time:
.post-card {
content-visibility: auto;
contain-intrinsic-size: 0 300px; /* approximate height to avoid layout shift */
}
This is particularly effective on Ghost homepages with large post grids.
Minimize use of {{#get}} on every page
The {{#get}} helper makes additional API calls during server-side rendering. If you use it on templates that render on every page (like a sidebar in default.hbs), you are adding a database query to every single page request. Use it thoughtfully and consider whether the data it fetches could be cached or included differently.
13. Third-Party Scripts and Their Performance Impact
Third-party scripts are often the biggest hidden performance tax on Ghost sites. Each script you add from an external source adds network requests, potential main thread blocking, and code that executes outside your control.
Common third-party scripts on Ghost sites and their costs
Analytics (Google Analytics 4, Plausible, Fathom): GA4 loads a script from Google's CDN, which adds a DNS lookup and connection time. Lightweight alternatives like Plausible and Fathom have smaller scripts, respect privacy by default, and are worth considering from a performance perspective as well as a privacy one.
Comment systems (Disqus, Cove, Hyvor Talk): Disqus is notorious for loading many resources and affecting page performance significantly. If you need comments, a lighter alternative or native Ghost member commenting has less impact.
Social sharing scripts: Never load the native Twitter/X, Facebook, or LinkedIn share button scripts. They each load dozens of resources and track users across the web. Implement share buttons as plain links pointing to the relevant sharing URLs instead. They work identically for users and have zero performance cost.
Email marketing embeds: If you embed a Mailchimp, ConvertKit, or similar form directly using their provided embed code, the associated scripts load on every page. Consider loading the embed JavaScript lazily on user interaction (when a visitor clicks a "Subscribe" button) rather than on page load.
Loading third-party scripts responsibly
For any non-essential third-party script, delay loading until after the page is interactive. A common pattern is to load non-critical scripts after a short timeout or on the first user interaction:
// Load non-critical third-party script after user interacts
const loadScript = (src) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
document.body.appendChild(script);
};
// Load after first interaction or after 5 seconds, whichever comes first
let loaded = false;
const load = () => {
if (loaded) return;
loaded = true;
loadScript('https://third-party.example.com/script.js');
};
['mousedown', 'touchstart', 'keydown', 'scroll'].forEach(event => {
document.addEventListener(event, load, { once: true, passive: true });
});
setTimeout(load, 5000);
This pattern ensures the third-party script does not affect your initial page performance while still loading before most visitors interact with relevant page features.
The dns-prefetch and preconnect hints
For third-party domains you know you will connect to, add resource hints in your <head> to complete the DNS lookup and connection setup early:
<!-- Preconnect for most important third-party origins -->
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<!-- DNS prefetch for less critical third-party origins -->
<link rel="dns-prefetch" href="https://www.google-analytics.com">
Use preconnect (which does the full handshake) sparingly, for only the one or two most critical external domains. Use dns-prefetch for others.
14. Ghost Membership and Portal Performance
If your Ghost site uses memberships, Portal (Ghost's built-in membership UI) adds a JavaScript bundle to every page. This is unavoidable if you use memberships, but you can control how it interacts with your performance metrics.
Portal and LCP
Portal loads a script from Ghost's CDN that initializes the membership interface. This script is loaded asynchronously and should not block your LCP. However, if Portal is slow to initialize and triggers a layout shift (for example, if it dynamically injects a floating subscribe button that shifts content), it affects your CLS score.
Check whether your theme styles for Portal elements include explicit sizing so no layout shift occurs when they appear.
Conditional Portal loading
If certain pages on your site do not use memberships at all (for example, a purely informational landing page), you can disable Portal for those pages by not including the {{ghost_foot}} helper, or by using Ghost's custom page templates and conditional logic. However, this is rarely necessary and complicates your template logic. Only do this if Portal is measurably affecting your performance on specific pages.
Optimizing the member signup flow
When a reader clicks your subscribe CTA and Portal opens, the experience needs to feel instantaneous. A few things help:
The Portal script should already be loaded and initialized before the reader clicks. Ghost handles this by loading Portal as a deferred script. As long as {{ghost_foot}} is at the bottom of your page and your page loads reasonably quickly, Portal will be ready.
Your CTA buttons and modals should have no noticeable animation lag. Test the signup flow on a throttled connection (Chrome DevTools > Network > Slow 3G) to experience what a reader on a mobile device with a weak connection goes through.
15. Monitoring and Maintaining Performance Over Time
Performance is not a one-time project. It decays. New content, new features, new third-party scripts, and Ghost updates all have the potential to affect your scores. A monitoring strategy keeps you from discovering problems only when they affect your search rankings.
Set up Google Search Console alerts
Search Console sends email notifications when Core Web Vitals issues are detected across a significant number of URLs. Make sure these notifications go to an inbox you check. When you receive one, investigate promptly. The earlier you catch a regression, the smaller its impact on your rankings.
Use a synthetic monitoring tool
Synthetic monitoring runs automated performance tests on a schedule and alerts you when scores drop below a threshold. Options include:
- SpeedCurve (paid, excellent dashboards and filmstrip comparisons)
- Calibre (paid, Ghost-compatible, clean interface)
- Treo (paid, CrUX-based monitoring)
- WebPageTest API with a custom script (free but requires setup)
Set up monitoring for your homepage and one typical post page at minimum. Run tests daily or on every content deployment.
Create a performance budget
A performance budget is a set of limits you define for your site's performance metrics. When a change you make pushes a metric over the limit, that is a signal to investigate before deploying. Common budget constraints for Ghost sites:
- Total page weight: under 500KB for the homepage, under 800KB for a typical post with a feature image
- Total number of HTTP requests: under 30
- LCP: under 2.0 seconds (more conservative than Google's 2.5-second threshold, to give yourself margin)
- JavaScript size: under 150KB uncompressed
- CSS size: under 50KB uncompressed
These numbers are starting points. Adjust them based on your site's specific context and audience.
Regular performance audits
Schedule a quarterly performance review where you:
- Run PageSpeed Insights on your three most-visited pages
- Check Google Search Console's Core Web Vitals report
- Review WebPageTest waterfall for any new resources that have crept in
- Audit your list of third-party scripts and remove any you are no longer using
- Check whether any of your images are loading at full resolution instead of a responsive size
This kind of regular attention prevents the gradual drift toward slowness that affects many sites over time.
Putting It All Together
Web performance optimization for Ghost CMS is a layered discipline. There is no single change that makes a slow site fast. It is a combination of using Ghost's built-in image processing correctly, writing lean CSS, loading JavaScript responsibly, choosing appropriate hosting, setting up caching and a CDN, managing third-party scripts carefully, and monitoring over time.
The good news is that Ghost gives you a genuinely strong starting point. You are not fighting against a bloated platform. You are adding deliberate optimization work on top of a foundation that was designed for publishing performance.
If you want to dive deeper into Ghost theme development and the craft of building fast, well-structured Ghost sites, the work being done at Anisul.com is worth exploring. Real-world Ghost theme development experience shines through in how performance considerations get baked in from the start rather than bolted on at the end.
Start with measurement. Find your biggest bottleneck. Fix it. Measure again. Repeat. That process, applied consistently, will get your Ghost site to the kind of performance that actually moves the needle for both your readers and your search rankings.