Your HubSpot site looks great in the page editor. Then you run it through PageSpeed Insights and the score comes back at 54 on mobile. You check the report, and it is full of terms like LCP, CLS, INP, render blocking resources, and a long list of scripts you did not add. Welcome to the reality of Core Web Vitals on HubSpot CMS.
Here is the thing most agencies will not tell you: a significant chunk of your performance overhead on HubSpot is platform level. HubSpot injects its own tracking scripts, chat widget code, form JavaScript, and analytics bundles on every single page. You cannot remove them. But that does not mean you are stuck with a slow site. There is plenty you can control, and the fixes are more straightforward than you might expect.
This guide covers what actually moves the needle on HubSpot CMS, what you are wasting time on, and where the platform itself is the bottleneck.
Google uses three metrics to judge how your pages feel to real visitors:
Google evaluates these at the 75th percentile of real user data. That means 75% of your actual visitors need to have a good experience for the page to pass. Lab tools like Lighthouse give you a synthetic score, but Google ranks you based on field data from the Chrome User Experience Report.
Every HubSpot CMS page loads a baseline set of scripts automatically. These include bundle.production.js, bundles/project.js, bundles/visitor.js, and bundles/app.js. They handle analytics tracking, the conversations widget, form rendering, and other platform features.
HubSpot says these load asynchronously and should not block rendering. That is technically true. But they still consume bandwidth, compete for the main thread on mobile devices, and show up as warnings in PageSpeed Insights. On a mid range Android phone over 4G, those scripts add real processing time.
You cannot remove them. You cannot defer them further. What you can do is make sure everything you control loads as efficiently as possible, so these platform scripts have less to compete with.
The single biggest LCP win on most HubSpot sites is the hero image. Content editors upload a 4000px wide photo straight from their camera roll or stock library, and HubSpot serves it as is. A 2MB hero image will blow your LCP budget every time.
What to do:
resize_image_url function to serve the right size for each breakpoint. This function generates a URL for a resized version of your image on HubSpot's CDN. You still need to build the srcset attribute yourself:<img
src=""
srcset=" 480w,
800w,
1200w,
1600w"
sizes="100vw"
alt=""
>
<head>. This tells the browser to start fetching the image before it even parses your CSS:<link rel="preload" as="image" href="">
loading="lazy" attribute tells the browser to wait until the image is near the viewport. Your hero is already in the viewport on load. Lazy loading it delays LCP.Every image below the fold should use loading="lazy". HubSpot's image HubL tags now support the loading attribute natively. In custom modules, add show_loading and loading keys to the image field in fields.json, or just set it directly in your module HTML:
<img src="" alt="" loading="lazy">
This is free performance. Every image you lazy load is bandwidth and main thread time that gets shifted from initial page load to when the visitor actually scrolls to that content.
If your modules load JavaScript in the <head>, the browser has to download and parse that script before it can render anything. That directly hurts LCP and INP.
HubSpot gives you two ways to fix this:
In meta.json (for module.js):
{
"js_render_options": {
"position": "footer",
"async": true
}
}
In module.html (for external scripts):
The difference between async and defer: async downloads the script in parallel and executes it immediately when ready (execution order is not guaranteed). Defer downloads in parallel but waits until the HTML is fully parsed before executing (maintains order). For most module scripts, defer is what you want.
This one is rampant in HubSpot themes. A carousel module loads Slick Slider (jQuery dependency) on every page, even pages without a carousel. A lightbox module loads its CSS and JS globally through the theme's main stylesheet.
Load libraries conditionally. Only pull in resources when the module that needs them is actually on the page:
And stop using jQuery. Every interaction you build with jQuery can be done with vanilla JavaScript. jQuery adds 85 KB (minified) of code your visitors' browsers need to download and parse. On mobile that is a meaningful INP hit. Modern browsers have querySelectorAll, addEventListener, fetch, classList, and everything else you were using jQuery for.
Fonts are a sneaky LCP killer. If your heading font has not loaded when the browser tries to render the hero, it either shows invisible text (flash of invisible text, or FOIT) or swaps in a fallback (flash of unstyled text, or FOUT). Both can delay LCP and cause layout shifts that hurt CLS.
Good news: HubSpot recently changed how Google Fonts load. If you select fonts through theme or module fields, HubSpot now serves them from your own domain using @font-face declarations instead of fetching from fonts.googleapis.com. This eliminates the third party connection overhead.
What you should still do:
font-display: swap in your @font-face rules. This tells the browser to show text immediately with a fallback font, then swap in the custom font when it loads. No invisible text, no LCP delay.<head>:<link rel="preload" as="font" type="font/woff2" href="/path/to/heading-font.woff2" crossorigin>
CLS happens when the browser does not know how big something will be before it loads. The image pops in and pushes everything below it down the page. The font swaps and the heading changes height. An ad slot or embed loads late and shoves content sideways.
The fix is straightforward: tell the browser how much space to reserve.
width and height attributes on <img> tags. The browser uses these to calculate the aspect ratio and reserve space before the image loads.aspect-ratio on image containers instead of padding hacks:.image-wrapper {
aspect-ratio: 16 / 9;
overflow: hidden;
}
.image-wrapper img {
width: 100%;
height: 100%;
object-fit: cover;
}
height: 100% on image wrappers without a defined parent height. This is a guaranteed layout shift.After HubSpot's own scripts, the next biggest performance drain is usually the pile of third party tools your marketing team has added over the months. Google Tag Manager loading six tracking pixels. Hotjar. Drift or Intercom on top of HubSpot's own chat. A social proof widget. An accessibility overlay. Each one adds JavaScript that fights for the main thread.
Run a quick audit:
setTimeout or trigger it on the first user interaction:document.addEventListener('scroll', function loadAnalytics() {
// Load your tracking script here
document.removeEventListener('scroll', loadAnalytics);
}, { once: true });
Many HubSpot modules apply colors, spacing, and other dynamic values as inline styles on individual elements. The problem is that inline styles cannot be cached separately, they bloat the HTML payload, and they make responsive overrides nearly impossible without !important.
A better pattern: set dynamic values as CSS custom properties on the module wrapper, then reference them in your stylesheet:
<!-- In module.html -->
<section class="my-module" style="--module-bg: ; --module-padding: px;">
...
</section>
<!-- In your CSS -->
.my-module {
background-color: var(--module-bg, #ffffff);
padding: var(--module-padding, 48px) 0;
}
This keeps your HTML lighter, your CSS maintainable, and your responsive breakpoints clean. It also reduces parsing time because the browser handles CSS custom properties more efficiently than scattered inline style declarations.
Some things on HubSpot CMS are outside your control. It helps to know what they are so you stop chasing ghosts:
Focus your energy on what you own: images, fonts, your JavaScript, your CSS, your module architecture, and third party scripts.
Run your site through PageSpeed Insights right now. Look at the field data section (real users), not just the lab data. Then work through this list:
font-display: swap?You will not get a perfect score. Nobody on HubSpot CMS does. But you can get a fast, responsive site that passes Core Web Vitals in the field, and that is what Google actually cares about when it comes to rankings.