HubSpot Forms blocking and affecting Core Web Vitals - here's the fix

HubSpot Forms blocking and affecting Core Web Vitals - here's the fix
Photo: PageVitals
Lasse Schou

Lasse Schou

21 July 2023

HubSpot Forms is an easy way for marketing teams to add forms to your website without requiring scarce developer time. You build your form, and add them with a simple JS script.

There is one big problem however: The script required to load the forms loads synchronously, blocking all other scripts and rendering of the page. This means that Core Web Vitals such as LCP and FCP are heavily affected. For other reasons, both CLS and INP may also be affected by these forms, but let's focus on the blocking issue. It's actually pretty unbelievable that HubSpot hasn't fixed the issue themselves.

The old way

HubSpot recommends adding the form using these two scripts:

<script src="//js.hsforms.net/forms/v2.js"></script>
<script>
hbspt.forms.create({
target: "#hubspotTarget",
portalId: "12345678",
formId: "xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
onFormReady: forms => { }
});
</script>

The first script referencing js.hsforms.net does not have a defer or async attribute which means that all rendering and other scripts are blocked while the HubSpot script is downloaded, parsed and executed. That's really bad, and unnecessary.

You can see it directly in the network waterfall:

HubSpot Forms affecting Core Web Vitals

Note how all the other scripts and images have to wait for the script to be requested (green), be downloaded (blue), and execute (the long task marked with red behind the "1.7s" marker).

You'll also note that the script requires 165KB to download. We won't be commenting too much on that part here, but it's a lot just to render a form - even a smart form.

The fix

Luckily there's an easy fix for this that doesn't require you to rebuild the forms manually.

First, add a defer attribute to the script reference.

<script src="//js.hsforms.net/forms/v2.js" defer></script>

The JavaScript code that invokes the form cannot run immediately because the script isn't loaded at this point. Instead, we push the function to a callback called hsFormsOnReady like this:

<script>
(window.hsFormsOnReady ||= []).push(_ => {
hbspt.forms.create({
target: "#hubspotTarget",
portalId: "12345678",
formId: "xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
onFormReady: forms => { }
});
});
</script>

And voilĂ , the HubSpot form now loads asynchronously, and you can still customize the forms from HubSpot's UI.

The complete code now looks like this:

<script src="//js.hsforms.net/forms/v2.js" defer></script>
<script>
(window.hsFormsOnReady ||= []).push(_ => {
hbspt.forms.create({
target: "#hubspotTarget",
portalId: "12345678",
formId: "xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
onFormReady: forms => { }
});
});
</script>

Remember to replace the target, portalId and formId with your own values.

Kudos to Joshua Clare-Flagg for coming up with this easy and clever solution.

Want to take PageVitals for a spin?

Page speed monitoring and alerting for your website. Get daily Lighthouse reports for all your pages. No installation needed.

Start my free trial