Script Loading
Introduction
The useScript composable provides a powerful way to load and manage external scripts in your application. Built on top of useHead(), it offers advanced features for script loading, performance optimization, and safe script interaction.
Script Singleton Pattern
A key feature of useScript is its singleton pattern - scripts with the same source or key are only loaded once globally, regardless of how many components request them.
import { useScript } from '@unhead/solid-js'
// In component A
useScript('https://maps.googleapis.com/maps/api/js')
// In component B - reuses the same script instance, doesn't load twice
useScript('https://maps.googleapis.com/maps/api/js')
Creating Reusable Script Composables
For better organization and reuse, wrap script initialization in dedicated composables:
// composables/useGoogleMaps.ts
import { useScript } from '@unhead/solid-js'
export function useGoogleMaps(options = {}) {
return useScript({
src: 'https://maps.googleapis.com/maps/api/js',
key: 'google-maps',
...options
})
}
Default Behavior & Performance
By default, useScript is configured for optimal performance and privacy:
Performance Attributes
- Scripts load after hydration by default for better performance
async: true- Load without blocking renderdefer: true- Execute in document order after page has loadedfetchpriority: 'low'- Prioritize other critical resources first
Privacy Attributes
crossorigin: 'anonymous'- Prevent third-party cookie accessreferrerpolicy: 'no-referrer'- Block referrer headers to script domain
Proxied Function Calls
The proxy feature allows you to safely call script functions even before the script has loaded:
import { useScript } from '@unhead/solid-js'
const { proxy } = useScript('https://www.googletagmanager.com/gtag/js')
// Works immediately, even if script hasn't loaded yet
proxy.gtag('event', 'page_view')
These function calls are queued and executed once the script loads. If the script fails to load, the calls are silently dropped.
Benefits of the Proxy Pattern
- Works during server-side rendering
- Resilient to script blocking (adblockers, etc.)
- Maintains function call order
- Allows script loading anytime without breaking application logic
Limitations
- Cannot synchronously get return values from function calls
- May mask loading issues (script failing silently)
- More difficult to debug than direct calls
- Not suitable for all script APIs
Direct API Access
For direct access to the script's API after loading:
import { useScript } from '@unhead/solid-js'
const { onLoaded } = useScript('https://www.googletagmanager.com/gtag/js')
onLoaded(({ gtag }) => {
// Direct access to the API after script is loaded
const result = gtag('event', 'page_view')
console.log(result)
})
Loading Triggers
Control when scripts load using triggers:
import { useScript } from '@unhead/solid-js'
// Load after a timeout
useScript('https://example.com/analytics.js', {
trigger: new Promise(resolve => setTimeout(resolve, 3000))
})
// Load on user interaction
useScript('https://example.com/video-player.js', {
trigger: (load) => {
// Only runs on client
document.querySelector('#video-container')
?.addEventListener('click', () => load())
}
})
// Manual loading (useful for lazy loading)
const { load } = useScript('https://example.com/heavy-library.js', {
trigger: 'manual'
})
// Load when needed
function handleSpecialFeature() {
load()
// Rest of the feature code...
}
Resource Warmup Strategies
Optimize loading with resource hints to warm up connections before loading the script:
import { useScript } from '@unhead/solid-js'
useScript('https://example.com/script.js', {
// Choose a strategy
warmupStrategy: 'preload' | 'prefetch' | 'preconnect' | 'dns-prefetch'
})
Strategy Selection Guide
preload- High priority, use for immediately needed scriptsprefetch- Lower priority, use for scripts needed soonpreconnect- Establish early connection, use for third-party domainsdns-prefetch- Lightest option, just resolves DNSfalse- Disable warmup entirely- Function - Dynamic strategy based on conditions
Manual Warmup Control
For granular control over resource warming:
import { useScript } from '@unhead/solid-js'
const script = useScript('https://example.com/video-player.js', {
trigger: 'manual'
})
// Add warmup hint when user might need the script
function handleHoverVideo() {
script.warmup('preconnect')
}
// Load when definitely needed
function handlePlayVideo() {
script.load()
}
Complete Example
import { useScript } from '@unhead/solid-js'
const analytics = useScript({
src: 'https://example.com/analytics.js',
key: 'analytics',
defer: true,
async: true,
crossorigin: 'anonymous',
referrerpolicy: 'no-referrer'
}, {
warmupStrategy: 'preconnect',
trigger: new Promise((resolve) => {
// Load after user has been on page for 3 seconds
setTimeout(resolve, 3000)
})
})
// Track page view immediately (queued until script loads)
analytics.proxy.track('pageview')
// Access direct API after script is loaded
analytics.onLoaded(({ track }) => {
// Do something with direct access
const result = track('event', { category: 'engagement' })
console.log('Event tracked:', result)
})
// Handle errors
analytics.onError((error) => {
console.error('Failed to load analytics:', error)
})
Best Practices
- Use composables to encapsulate script initialization logic
- Consider user privacy when loading third-party scripts
- Use appropriate warmup strategies based on script importance
- Add error handling for critical scripts
- Use triggers to control loading timing for better performance
- Be mindful of proxy limitations for complex script APIs
Common Use Cases
Google Analytics
export function useGoogleAnalytics() {
const script = useScript({
src: 'https://www.googletagmanager.com/gtag/js',
defer: true
})
// Initialize GA
script.proxy.gtag('js', new Date())
script.proxy.gtag('config', 'G-XXXXXXXXXX')
return {
...script,
trackEvent: (category, action, label) => {
script.proxy.gtag('event', action, {
event_category: category,
event_label: label
})
}
}
}