---
title: "useScript()"
description: "Load third-party scripts with useScript(). Smart defaults for performance, lazy loading triggers, and API proxying for analytics and widgets."
canonical_url: "https://unhead.unjs.io/docs/head/api/composables/use-script"
last_updated: "2026-05-02T20:01:30.087Z"
---

The `useScript` composable provides an enhanced developer experience for loading third-party scripts with intelligent defaults for performance, security, and lifecycle management.

**Quick Start:**

```ts
const { proxy, onLoaded } = useScript('https://example.com/analytics.js')

// Call functions immediately (queued until loaded)
proxy.track('pageview')

// Or wait for script to load
onLoaded(() => {
  console.log('Script ready!')
})
```

## Basic Usage

```ts
import { useScript } from '@unhead/dynamic-import'

const { onLoaded } = useScript('https://example.com/script.js')

onLoaded(() => {
  // Script loaded successfully
  console.log('Script is ready to use')
})
```

## Smart Defaults

A singleton pattern is implemented so scripts with the same `src` or `key` are only loaded once globally. This helps prevent duplicate script loading and ensures consistent initialization.

The following defaults are applied for optimal performance and security:

- Scripts load after hydration by default
- `defer` enabled for proper execution order
- `fetchpriority="low"` to prioritize critical resources
- `crossorigin="anonymous"` prevents cookie access
- `referrerpolicy="no-referrer"` blocks referrer headers

## Input Options

### Simple URL

Pass a URL string for the quickest implementation:

```ts
import { useScript } from '@unhead/dynamic-import'

useScript('https://example.com/script.js')
```

### Full Configuration

Pass an options object to customize any `<script>` attribute:

```ts
import { useScript } from '@unhead/dynamic-import'

useScript({
  src: 'https://example.com/script.js',
  id: 'my-script',
  async: true,
  defer: false,
  crossorigin: false, // disable crossorigin='anonymous'
  // Any valid script attribute can be used
})
```

## Loading Control

Fine-tune when and how scripts load with the second parameter:

```ts
import { useScript } from '@unhead/dynamic-import'

useScript('https://example.com/script.js', {
  // When to load the script
  trigger: 'client', // | 'server' | Promise | ((load) => void)

  // Resource hint strategy
  warmupStrategy: 'preload', // | 'prefetch' | 'preconnect' | 'dns-prefetch',

  // Access the script's API
  use: () => window.externalAPI
})
```

### Loading Triggers

Control precisely when scripts load with different trigger strategies:

```ts
import { useScript } from '@unhead/dynamic-import'

// Load immediately on the client (default)
useScript(src, { trigger: 'client' })

// Load during server rendering
useScript(src, { trigger: 'server' })

// Load when a promise resolves
useScript(src, {
  trigger: new Promise(resolve =>
    setTimeout(resolve, 3000)
  )
})

// Custom load function
useScript(src, {
  trigger: (load) => {
    document.getElementById('load-button').addEventListener('click', load)
  }
})
```

## Script Lifecycle

The script passes through these lifecycle states:

- `awaitingLoad` - Initial state
- `loading` - Script is loading
- `loaded` - Script loaded successfully
- `error` - Script failed to load
- `removed` - Script was removed

Monitor these states with lifecycle hooks:

```ts
import { useScript } from '@unhead/dynamic-import'

const script = useScript('https://example.com/script.js')

script.onLoaded((api) => {
  // Script loaded successfully
  console.log('Script is ready')
})

script.onError(() => {
  // Script failed to load
  console.error('Script loading failed')
})
```

## Resource Hints

The `warmupStrategy` option automatically adds resource hints to optimize loading:

```ts
import { useScript } from '@unhead/dynamic-import'

useScript('https://example.com/script.js', {
  // Preload - highest priority, load ASAP
  warmupStrategy: 'preload',

  // Prefetch - load when browser is idle
  warmupStrategy: 'prefetch',

  // Preconnect - setup connection early
  warmupStrategy: 'preconnect',

  // DNS Prefetch - resolve DNS early
  warmupStrategy: 'dns-prefetch'
})
```

<tip>

Choose the right strategy based on how critical the script is:

- Use `preload` for essential scripts needed soon after page load
- Use `prefetch` for scripts needed later in the user journey
- Use `preconnect` or `dns-prefetch` to optimize third-party domains

</tip>

## API Proxying

If you need to access the script's API before it loads, use the `use` option with proxy support:

```ts
import { useScript } from '@unhead/dynamic-import'

const script = useScript({
  src: 'https://maps.googleapis.com/maps/api/js'
}, {
  use: () => window.google.maps
})

// Works before script loads!
const map = script.proxy.Map()
```

The proxy records all calls and replays them once the script is loaded, allowing you to use the API immediately in your code without worrying about loading state.

## Common Use Cases

### Google Tag Manager

```ts
import { useScript } from '@unhead/dynamic-import'

useScript({
  src: 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX',
  async: true
}, {
  trigger: 'client'
})

// Initialize gtag
useHead({
  script: [
    {
      children: `
        window.dataLayer = window.dataLayer || [];
        function gtag(){dataLayer.push(arguments);}
        gtag('js', new Date());
        gtag('config', 'G-XXXXXXXXXX');
      `,
      key: 'gtag-config'
    }
  ]
})
```

### Loading on User Interaction

```ts
import { useScript } from '@unhead/dynamic-import'

// Load YouTube player only when user clicks play
useScript('https://www.youtube.com/iframe_api', {
  trigger: (load) => {
    document.getElementById('play-video').addEventListener('click', () => {
      load()
      // Show loading indicator while script is loading
    })
  }
})
```

## API Reference

### Input

- `src`: String URL or object with script attributes

### Options

- `trigger`: When to load the script ('client', 'server', Promise, or custom function)
- `warmupStrategy`: Resource hint strategy ('preload', 'prefetch', 'preconnect', 'dns-prefetch')
- `use`: Function to access the script's API

### Return Value

Returns a script controller object with these properties:

- `status`: Current lifecycle state
- `onLoaded`: Register callback for successful load
- `onError`: Register callback for loading failure
- `proxy`: Proxy to the script's API (if `use` option provided)

## TypeScript

Type the script's API with generics:

```ts
import { useScript } from '@unhead/vue'

interface GoogleMaps {
  Map: new (el: HTMLElement, options: object) => object
  Marker: new (options: object) => object
}

const { proxy } = useScript<GoogleMaps>({
  src: 'https://maps.googleapis.com/maps/api/js'
}, {
  use: () => window.google?.maps as GoogleMaps
})

// proxy is now typed
proxy.Map(document.getElementById('map')!, { zoom: 10 })
```

## Common Mistakes

### Not handling script load failures

```ts
// ❌ Assumes script always loads
const { proxy } = useScript('https://cdn.example.com/analytics.js')
proxy.track('pageview') // Fails silently if blocked

// ✅ Handle errors gracefully
const { proxy, onError, status } = useScript('https://cdn.example.com/analytics.js')
onError(() => {
  console.warn('Analytics blocked - falling back to internal tracking')
})
```

### Loading scripts too eagerly

```ts
// ❌ Loads heavy library immediately
useScript('https://cdn.example.com/heavy-widget.js')

// ✅ Defer until needed
useScript('https://cdn.example.com/heavy-widget.js', {
  trigger: (load) => {
    document.getElementById('widget-container')?.addEventListener('mouseenter', load, { once: true })
  },
  warmupStrategy: 'preconnect' // Warm up connection early
})
```

### Using window globals before script loads

```ts
// ❌ Race condition - script may not be loaded
useScript('https://example.com/lib.js')
window.MyLib.init() // TypeError: Cannot read property 'init' of undefined

// ✅ Use proxy or onLoaded
const { proxy, onLoaded } = useScript('https://example.com/lib.js', {
  use: () => window.MyLib
})

// Option 1: Proxy (queues calls)
proxy.init()

// Option 2: Wait for load
onLoaded((lib) => {
  lib.init()
})
```

## Common Questions

### How do I load a script only on user interaction?

Use the `trigger` option with a custom function and call `load()`:

```ts
const { load } = useScript('heavy-library.js', {
  trigger: (load) => {
    button.onclick = () => load()
  }
})
```

### Why use proxy instead of direct API calls?

The proxy queues calls until the script loads, making your code resilient to loading delays and adblockers.

## See Also

- [useHead()](/docs/head/api/composables/use-head) - General head management
- [useSeoMeta()](/docs/head/api/composables/use-seo-meta) - SEO meta tag management
