Composables

useScript

Load third-party scripts with SSR support and a proxied API.

Experimental in v1.8

Warn: The API is still experimental and may change in the future.

Use non-bundled IIFE third-party scripts a breeze with a focus on performance and DX.

It's recommended to use when you want to use any non-blocking third-party: Google Analytics, Stripe, etc. However, it is low-level and is better served as a building block for higher-level APIs useGoogleAnalytics, etc.

Features:

  • 🦥 Lazy, but fast: defer, fetchpriority: 'low', early connections (preconnect, dns-prefetch)
  • ☕ Loading strategies: idle, manual
  • 🪨 Single script instance for your app
  • 🎃 Events for SSR scripts: onloading, onerror, etc
  • 🪝 Proxy API: call the script functions before it's loaded, noop for SSR, stubbable, etc
  • 🇹 Fully typed APIs

Background

Loading scripts using the useHead composable is easy.

useHead({
  scripts: [
    { src: 'https://www.google-analytics.com/analytics.js' }
  ]
})

However, when loading a third-party script, you often want to access some functionality provided by the script.

For example, Google Analytics provides a gtag function that you can use to track events.

// We need to load first: https://www.google-analytics.com/analytics.js
gtag('event', 'page_view', {
  page_title: 'Home',
  page_location: 'https://example.com',
  page_path: '/',
})

The API provided by these scripts doesn't work in a SSR environment or if the script isn't loaded yet. Leading to a jumbled mess of trying to make sure we can use the API. For TypeScript you'll need to augment global window types to use the API effectively.

The useScript composable aims to solve these issues and make working with third-party scripts a breeze.

interface GoogleTag {
  gtag: ((fn: 'event', opt: string, opt2: { [key: string]: string }) => void)
}
const { gtag } = useScript<GoogleTag>({
  src: 'https://www.google-analytics.com/analytics.js',
}, {
  trigger: 'idle',
  use: () => { gtag: window.gtag },
})
// fully typed, usable in SSR and when lazy loaded
gtag('event', 'page_view', {
  page_title: 'Home',
  page_location: 'https://example.com',
  page_path: '/',
})

We can even go a step further, say we want to send API requests on the server, we can conditionally stub the API.

const { gtag } = useScript<GoogleTag>({
  src: 'https://www.google-analytics.com/analytics.js',
}, {
  use: () => { gtag: window.gtag },
  stub: () => {
    if (process.server) {
      return (fn: 'event', opt: string, opt2: { [key: string]: string }) => {
        // send fetch to ga
        return fetch('https://www.google-analytics.com/analytics.js', {
          method: 'POST',
          body: JSON.stringify({ event: opt, ...op2 })
        })
      }
    }
  }
})

Usage

Arguments

useScript<API>(scriptOptions, options)

scriptOptions

The script options, this is the same as the script option for useHead. For example src, async, etc.

useScript({
  key: 'google-analytics', // custom key
  src: 'https://www.google-analytics.com/analytics.js',
  async: true,
  defer: true,
})

options

  • skipEarlyConnections

Used to skip early connections such as dns-prefetch and preconnect.

Useful when you're loading a script from your own domain.

  • use

A function that resolves the scripts API. This is only called client-side.

const { trackPageview } = useScript<FathomApi>({
  // fathom analytics
  src: 'https://cdn.usefathom.com/script.js',
}, {
  use: () => window.fathom
})
// just works
trackPageview({ url: 'https://example.com' })
  • trigger

An optional loading strategy to use. idle or manual. Defaults to undefined.

// will load on idle
useScript({
  src: 'https://example.com/script.js',
}, {
  trigger: 'idle'
})
  • stub

A more advanced function used to stub out the logic of the API. This will be called on the server and client.

const { sendEvent, doSomething } = useScript<MyScriptApi>({
  src: 'https://example.com/script.js',
}, {
  use: () => window.myScript,
  stub: ({ fn }) => {
    // stub out behavior on server
    if (process.server && fn === 'sendEvent')
      return (opt: string) => fetch('https://api.example.com/event', { method: 'POST', body: opt })
  }
})
// on server, will send a fetch to https://api.example.com/event
// on client it falls back to the real API
sendEvent('event')
// on server, will noop
// on client it falls back to the real API
doSomething()

$script

The return value is an object with the API provided by the script. It also contains a special $script property that gives you access to the underlying script instance.

const { $script } = useScript({
  // ...
})
  • status

The status of the script. Can be one of the following: 'awaitingLoad' | 'loading' | 'loaded' | 'error'

  • loaded

A boolean for if the script has loaded.

  • load

Trigger the script to load. This is useful when using the manual loading strategy.

const { $script } = useScript({
  // ...
}, {
  trigger: 'manual'
})
// ...
$script.load()
  • waitForLoad

A promise that resolves when the script is ready to use. This can be useful when you don't want to use the mock API.

const { $script, doSomething } = useScript({
  // ...
}, { use: () => window.myScript })
// will only run client-side, once the script is ready
$script.waitForLoad().then(() => {
  // ...
  doSomething() // will always be called immediately
})

Examples

Google Analytics

Unhead
import { useScript } from 'unhead'

const { gtag } = useScript({
  src: 'https://www.google-analytics.com/analytics.js',
}, {
  use: () => { gtag: window.gtag }
})