Home / Blog / Load Third-Party Scripts in Nuxt: useHead vs useScript vs Nuxt Scripts
Load Third-Party Scripts in Nuxt: useHead vs useScript vs Nuxt Scripts

Load Third-Party Scripts in Nuxt: useHead vs useScript vs Nuxt Scripts

Analytics, payment SDKs, chat widgets, maps: third-party scripts are easy to drop in and easy to get wrong. Load one carelessly and it blocks rendering, drags down your Core Web Vitals, and fires before the user has agreed to anything. Nuxt now gives you three ways to handle this, from a plain script tag to a purpose-built module that ships optimized wrappers for the services you use most.

This guide compares all three. You get the code for each, the trade-offs, and a clear recommendation on which to reach for. If you want the short answer, read the decision table below, then jump to the approach that fits your case.

Quick Comparison: Which Approach Should You Use?

Approach Best for What you get Loading control Effort
useHead A one-off tag you just need in the document <head> Manual <script> injection, SSR-safe You set async / defer; knowing when it is ready is on you Low
useScript A script you interact with in code (call its global, wait for it, avoid loading it twice) Load status you can await, deduplication, trigger strategies Load on idle, on visibility, on interaction, or on consent Medium
Nuxt Scripts module Common services (Google Analytics, Stripe, Maps, YouTube, Meta Pixel) Typed registry wrappers, facade components, privacy-first defaults Built-in trigger strategies plus consent helpers Low to Medium

Loading a well-known third party (Google Analytics, Stripe, Google Maps, YouTube, Meta Pixel)? Use the Nuxt Scripts module. Its registry gives you a typed wrapper that handles the loading, the ready state, and sensible privacy defaults for you, and its facade components render a lightweight placeholder until the real embed is actually needed. This is the path to reach for first.

Loading a script you need to talk to in code? Use useScript. You get a load status you can await and a callback that runs once the script is ready, so you stop guessing whether the global exists yet. Its trigger strategies let the script load on idle, on first interaction, or when an element scrolls into view.

Just need a tag in the head and nothing more? useHead is fine. Set defer or async and move on. Do not use it for anything where your code has to know the script finished loading; that is what useScript is for.

Rule of thumb: if a registry wrapper exists for your service, use it. If not, use useScript. Fall back to useHead only for a passive tag.

Introduction to Third-Party Scripts

Almost every modern web application relies on external services alongside its primary JavaScript framework.

While many dependencies are available via NPM, many third-party services provide a CDN link intended for direct inclusion via script tags. Common examples include newsletter sign-up widgets, chat support, and the Stripe SDK for payment processing.

Using a CDN link allows vendor updates without requiring dependency bumps, but it introduces complexity—especially when scripts are needed on specific pages and must load asynchronously without blocking user interactions in your Nuxt project.

Loading Third-Party Scripts Globally in Nuxt

When a script is required on every page of your application, loading it globally is simple. You can define it inside the head configuration of your nuxt.config.ts, and Unhead will manage tag injection and deduplication.

// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      title: "My awesome project",
      script: [
        { key: "stripe", src: "https://js.stripe.com/v3/", defer: true }
      ]
    }
  }
})

Note: Notice the use of the key attribute instead of the deprecated hid property. Unhead uses key for script deduplication.

While global loading works, deferring script loading across all pages can negatively impact performance metrics if the script is only needed on distinct routes like a checkout page.

Adding Third-Party Scripts to Distinct Pages with useHead

To restrict a script to specific routes, you can invoke the useHead composable within individual page components using Vue Composition API and Script Setup:

<script setup lang="ts">
useHead({
  title: "Payment Page - My awesome project",
  script: [
    { key: "stripe", src: "https://js.stripe.com/v3/", defer: true }
  ]
});
</script>

The Problem with Async Third-Party Scripts

When using Nuxt in SSR mode, handling asynchronously loaded third-party scripts introduces timing issues. During client-side navigation, the Vue component's mounted lifecycle hook may execute before the external script has finished downloading and parsing.

<template>
  <div>
    <!-- How do I know when Stripe is ready? -->
    <SomeComponentDependingOnStripe />
  </div>
</template>

<script setup lang="ts">
// /pages/payment-page.vue
useHead({
  title: "Payment Page - My awesome project",
  script: [{ key: "stripe", src: "https://js.stripe.com/v3/", defer: true }]
});
</script>

Historically, developers relied on hand-rolled onload callbacks inside useHead, tracking readiness with a local ref. While functional, hand-rolling load state management leads to boilerplate and race conditions. Today, Nuxt provides first-class primitives designed specifically for script lifecycle management.

The Modern Solution: useScript for Custom Scripts

For scripts that do not have dedicated module wrappers, the useScript composable provides reactive loading status, promise resolution, and configurable loading triggers without manual event listeners.

<template>
  <div>
    <p v-if="status === 'loading'">Loading external library...</p>
    <CustomWidget v-else-if="status === 'loaded'" />
    <p v-else-if="status === 'error'">Failed to load script.</p>
  </div>
</template>

<script setup lang="ts">
import { useScript } from '#imports'

const { status, onLoaded } = useScript('https://example.com/custom-widget.js', {
  trigger: 'onNuxtReady'
})

onLoaded(() => {
  console.log('Script is ready to use!')
})
</script>

With useScript, you can await script readiness, react to status updates ('awaitingLoad' | 'loading' | 'loaded' | 'error'), and control when the script loads using triggers like 'idle', 'visibility', or 'interaction'.

Nuxt Scripts Module: Optimized Wrappers & Facades

For well-known third-party services, the official Nuxt Scripts module (@nuxt/scripts) provides typed registry wrappers, privacy-first defaults, and facade components.

To get started, install the module in your Nuxt application:

npx nuxi module add scripts

Once installed, you can replace manual script tags with dedicated composables like useScriptStripe:

<template>
  <div>
    <div id="payment-element"></div>
    <p v-if="!ready">Loading secure checkout...</p>
  </div>
</template>

<script setup lang="ts">
const ready = ref(false)
const { onLoaded } = useScriptStripe()

onLoaded(({ Stripe }) => {
  const stripe = Stripe('YOUR_STRIPE_PUBLIC_KEY')
  const elements = stripe.elements()
  const paymentElement = elements.create('payment')
  paymentElement.mount('#payment-element')
  ready.value = true
})
</script>

Learn more about Nuxt composables and state management in the Nuxt 3 Fundamentals course on Vue School.

Facade Components for Heavy Embeds

Nuxt Scripts also includes built-in facade components for heavy third-party embeds like YouTube (<ScriptYouTubePlayer>) and Google Maps (<ScriptGoogleMaps>). A facade displays a lightweight UI placeholder first, deferring heavy iframe and script downloads until the user interacts with the component. This drastically reduces Total Blocking Time (TBT) and improves your Largest Contentful Paint (LCP).

Performance Optimization & Consent-Gated Loading

Loading third-party scripts efficiently is critical for privacy compliance and Core Web Vitals. Nuxt Scripts lets you gate script execution behind user consent triggers seamlessly:

<script setup lang="ts">
import { ref } from 'vue'

const hasUserConsented = ref(false)

// Script will not load until hasUserConsented becomes true
useScript('https://www.google-analytics.com/analytics.js', {
  trigger: hasUserConsented
})

function acceptCookies() {
  hasUserConsented.value = true
}
</script>

By pairing consent refs with trigger strategies, analytics and tracking tags remain fully compliant without requiring complex custom wrappers.

Frequently Asked Questions (FAQ)

Which should I use to load a third-party script in Nuxt?

If a Nuxt Scripts registry wrapper exists for the service (Google Analytics, Stripe, Maps, YouTube, Meta Pixel, etc.), use it. For any other script your code interacts with, use useScript. For a passive tag you just need in the head, useHead is enough.

How do I know when a third-party script has finished loading?

Do not hand-roll an onload handler and a boolean ref. Use useScript or a Nuxt Scripts registry wrapper. Both expose the load status and an onLoaded callback that runs once the script is ready, so you can safely render or initialize against the global object.

How do I stop a third-party script from slowing my page?

Load it lazily with a trigger strategy (on idle, on visibility, or on first interaction) instead of placing it in the initial head, and use a facade component for heavy embeds so a lightweight placeholder loads first and the real script loads only on engagement.

Can I load scripts only after cookie consent?

Yes. Gate loading behind a consent trigger (such as a reactive ref) so the script does not fetch or run until the user opts in. This keeps analytics and marketing tags compliant without extra glue code.

Is useHead still fine for scripts?

Yes, for a simple tag. It injects the <script> and respects SSR, but it does not track whether the script loaded. When timing matters, reach for useScript or a Nuxt Scripts wrapper.

Do I still need hid to deduplicate a script?

No. Use key to dedupe a script entry. hid is deprecated in current Unhead.

Conclusion

Managing third-party scripts in Nuxt.js has evolved into a clean, developer-friendly experience. By combining useHead for static tags, useScript for custom interactive scripts, and the official Nuxt Scripts module for major services and facade components, you can keep your applications fast, maintainable, and privacy-compliant.

To master Nuxt 3 patterns, script loading, and full-stack Vue development, check out the Nuxt 3 Fundamentals course and also explore advanced Vue.js 3 patterns in our flagship Vue.js 3 Master Class.

Which should I use to load a third-party script in Nuxt?

If a Nuxt Scripts registry wrapper exists for the service (Google Analytics, Stripe, Maps, YouTube, Meta Pixel and more), use it. For any other script your code interacts with, use useScript. For a passive tag you just need in the head, useHead is enough.

How do I know when a third-party script has finished loading?

Do not hand-roll an onload handler and a boolean ref. Use useScript or a Nuxt Scripts registry wrapper. Both expose the load status and a callback that runs once the script is ready, so you can render or initialize against the global safely.

How do I stop a third-party script from slowing my page?

Load it lazily with a trigger strategy (on idle, on visibility, or on first interaction) instead of placing it in the initial head, and use a facade component for heavy embeds so a lightweight placeholder loads first and the real script loads only on engagement.

Can I load scripts only after cookie consent?

Yes. Gate loading behind a consent trigger so the script does not run until the user opts in. This keeps analytics and marketing tags compliant without extra glue code.

Is useHead still fine for scripts?

Yes, for a simple tag. It injects the <script> and respects SSR, but it does not track whether the script loaded. When timing matters, reach for useScript or a Nuxt Scripts wrapper.

Do I still need hid to deduplicate a script?

No. Use key to dedupe a script entry. hid is deprecated in current Unhead. Verify against current docs.

Start learning Vue.js for free

Developmint, Nuxt.js core member and passionate full-stack dev who enjoys working with Tailwind, Nuxt.js and Laravel. Also blogging about related topics every now and then.

Comments

Latest Vue School Articles

5 Component Design Patterns to Boost Your Vue.js Applications

5 Component Design Patterns to Boost Your Vue.js Applications

5 essential Vue.js component design patterns, including branching components, slots usage, list organization, smart vs dumb components, and form handling - perfect for both Vue beginners and experienced developers looking to improve code maintainability and scalability.
Vibe Coding a Collaborative Editor with Comment Support with Nuxt UI and Jazz

Vibe Coding a Collaborative Editor with Comment Support with Nuxt UI and Jazz

Why I built a Nuxt + Jazz powered real time editor, how you can use it, and a list of takeaways on building with the help of AI.
VueSchool logo

Our goal is to be the number one source of Vue.js knowledge for all skill levels. We offer the knowledge of our industry leaders through awesome video courses for a ridiculously low price.

More than 250.000 users have already joined us. You are welcome too!

Follow us on Social