Introducing Nuxt Scripts
The Nuxt team, in collaboration with the Chrome Aurora team at Google, is excited to announce the public beta release of Nuxt Scripts.
Nuxt Scripts is a better way to work with third-party scripts, providing improved performance, privacy, security, and developer experience.
Getting to Nuxt Scripts
Over a year ago, Daniel published the initial Nuxt Scripts RFC. The RFC proposed a module that would "allow third-party scripts to be managed and optimized, following best practices for performant and compliant websites".
Having personal experience with solving performance issues related to third-party scripts, I knew how difficult these performance optimizations could be. Nonetheless, I was keen to tackle the problem and took over the project.
With the RFC as the seed of the idea, I started prototyping what it could look like using Unhead.
Thinking about what I wanted to build exactly, I found that the real issue wasn't just how to load "optimized" third-party scripts but how to make working with third-party scripts a better experience overall.
Why Build a Third-Party Script Module?
94% of sites use at least one third-party provider, with the average site having five third-party providers.
We know that third-party scripts aren't perfect; they slow down the web, cause privacy and security issues, and are a pain to work with.
However, they are fundamentally useful and aren't going anywhere soon.
By exploring the issues with third-party scripts, we can see where improvements can be made.
๐ Developer Experience: A Full-Stack Headache
Let's walk through adding a third-party script to your Nuxt app using a fictional tracker.js
script that adds a track
function to the window.
We start by loading the script using useHead
.
useHead({ scripts: [{ src: '/tracker.js', defer: true }] })
However, let's now try to get the script functionality working in our app.
The following steps are common when working with third-party scripts in Nuxt:
- Everything has to be wrapped for SSR safety.
- Flaky checks for if the script has loaded.
- Augmenting the window object for types.
<script setup>
// โ Oops, window is not defined!
// ๐ก The window can't be directly accessed if we use SSR in Nuxt.
// ๐ We need to make this SSR safe
window.track('page_view', useRoute().path)
</script>
๐ Performance: "Why can't I get 100 on Lighthouse?"
For a visitor to start interacting with your Nuxt site, the app bundle needs to be downloaded and Vue needs to hydrate the app instance.
Loading third-party scripts can interfere with this hydration process, even when using async
or defer
. This slows down the network and blocks the main thread, leading to a degraded user experience and poor Core Web Vitals.
The Chrome User Experience Report shows Nuxt sites with numerous third-party resources typically have poorer Interaction to Next Paint (INP) and Largest Contentful Paint (LCP) scores.
To see how third-party scripts degrade performance, we can look at the Web Almanac 2022. The report shows that the top 10 third-party scripts average median blocking time is 1.4 seconds.
๐ก๏ธ Privacy & Security: Do no evil?
Of the top 10,000 sites, 58% of them have third-party scripts that exchange tracking IDs stored in external cookies, meaning they can track users across sites even with third-party cookies disabled.
While in many cases our hands are tied with the providers we use, we should try to minimize the amount of our end-users' data that we're leaking where possible.
When we do take on the privacy implications, it can then be difficult to accurately convey these in our privacy policies and build the consent management required to comply with regulations such as GDPR.
Security when using third-party scripts is also a concern. Third-party scripts are common attack vectors for malicious actors, most do not provide integrity
hashes for their scripts, meaning they can be compromised and inject malicious code into your app at any time.
What does Nuxt Scripts do about these issues?
Composable: useScript
This composable sits between the <script>
tag and the functionality added to window.{thirdPartyKey}
.
For the <script>
tag, the composable:
- Gives full visibility into the script's loading and error states
- Loads scripts as Nuxt is hydrating the app by default, for slightly better performance.
- Restricts
crossorigin
andreferrerpolicy
to improve privacy and security. - Provides a way to delay loading the script until you need it.
For the scripts API, it:
- Provides full type-safety around the script's functions
- Adds a proxy layer allowing your app to run when the script functions in unsafe contexts (SSR, before the script is loaded, the script is blocked)
const { proxy, onLoaded } = useScript('/hello.js', {
trigger: 'onNuxtReady',
use() {
return window.helloWorld
}
})
onLoaded(({ greeting }) => {
// โ
script is loaded! Hooks into Vue lifecycle
})
// โ
OR use the proxy API - SSR friendly, called when script is loaded
proxy.greeting() // Hello, World!
declare global {
interface Window {
helloWorld: {
greeting: () => 'Hello World!'
}
}
}
Script Registry
The script registry is a collection of first-party integrations for common third-party scripts. As of release, we support 21 scripts, with more to come.
These registry scripts are fine-tuned wrappers around useScript
with full type-safety, runtime validation of the script options (dev only) and environment variable support
For example, we can look at the Fathom Analytics script.
const { proxy } = useScriptFathomAnalytics({
// โ
options are validated at runtime
site: undefined
})
// โ
typed
proxy.trackPageview()
Facade Components
The registry includes several facade components, such as Google Maps, YouTube and Intercom.
Facade components are "fake" components that get hydrated when the third-party script loads. Facade components have trade-offs but can drastically improve your performance. See the What are Facade Components? guide for more information.
Nuxt Scripts provides facade components as accessible but headless, meaning they are not styled by default but add the necessary a16y data.
Click to load
Consent Management & Element Event Triggers
The useScript
composable gives you full control over how and when your scripts are loaded, by either providing a custom trigger
or manually calling the load()
function.
Building on top of this, Nuxt Scripts provides advanced triggers to make it even easier.
- Consent Management - Load scripts only after the user has given consent such as with a cookie banner.
- Element Event Triggers - Load scripts based on user interactions such as scrolling, clicking, or form submissions.
const cookieConsentTrigger = useScriptTriggerConsent()
const { proxy } = useScript<{ greeting: () => void }>('/hello.js', {
// script will only be loaded once the consent has been accepted
trigger: cookieConsentTrigger
})
// ...
function acceptCookies() {
cookieConsentTrigger.accept()
}
// greeting() is queued until the user accepts cookies
proxy.greeting()
Bundling Scripts
In many cases, we're loading third-party scripts from a domain that we don't control. This can lead to a number of issues:
- Privacy: The third-party script can track users across sites.
- Security: The third-party script can be compromised and inject malicious code.
- Performance: Extra DNS lookups will slow down the page load.
- Developer Experience: Consented scripts may be blocked by ad blockers.
To mitigate this, Nuxt Scripts provides a way to bundle third-party scripts into your public directory without any extra work.
useScript('https://cdn.jsdelivr.net/npm/js-confetti@latest/dist/js-confetti.browser.js', {
bundle: true,
})
The script will now be served from /_scripts/{hash}
on your own domain.
To be continued
As we saw, there are many opportunities to improve third-party scripts for developers and end-users.
The initial release of Nuxt Scripts has solved some of these issues, but there's still a lot of work ahead of us.
The next items on the roadmap are:
- Add web worker support (Partytown)
- More Live Chat Facade Components
- Offload Scripts To Nuxt Server Proxy
- Iframe Script Sandboxing
We'd love to have your contribution and support.
Getting started
To get started with Nuxt Scripts, we've created a tutorial to help you get up and running.
Credits
- Harlan Wilton - Nuxt (author)
- Julien Huang - Nuxt (contributor)
- Daniel Roe - Nuxt (contributor)
- Chrome Aurora - Google (contributor)
And a big thank you to the early contributors.