Bundling Remote Scripts
bundle option is deprecated in favor of First-Party Mode, which provides the same benefits plus routed collection endpoints for improved privacy. Use firstParty: true for new projects.Background
When you use scripts from other sites on your website, you rely on another server to load these scripts. This can slow down your site and raise concerns about safety and privacy.
Common problems
- Slower website because it takes time to connect to other servers.
- Safety risks if the other server is hacked.
- Other servers may use your visitors' data inappropriately.
- Ad blockers or privacy tools might stop these scripts from working.
How to fix it
By bundling these scripts, you can host them yourself, which helps avoid these issues and keeps your site running smoothly.
How it Works
During the build process, Nuxt checks your code to find any instances of useScript() that need bundling.
When Nuxt identifies a script for bundling, it downloads and saves it as a public asset at /_scripts/[hash].js. Here, [hash] represents the hash of the script's URL.
Important points about bundling:
- You need to have static values for your script URLs and bundling settings.
// GOOD - Static values allow for bundling
useScript('https://example.com/script.js', {
bundle: true
})
// BAD - Dynamic values prevent bundling
useScript(scriptSrc, {
bundle: canBundle
})
// GOOD - Nuxt bundles the script
useScript('/_scripts/[hash].js', {})
// BAD - Nuxt does not bundle the script (remains the same)
useScript(scriptSrc, {
bundle: canBundle
})
- If the original script changes without a URL change, the bundled version won't update in the browser cache. To handle this, use a versioned URL or a cache-busting query parameter.
Usage
You can bundle scripts individually or on a global scale using specific settings.
Script Options
To decide if Nuxt can bundle an individual script, use the bundle option.
// Opt-in to bundle this specific script
useScript('https://example.com/script.js', {
bundle: true,
})
// Force download bypassing cache
useScript('https://example.com/script.js', {
bundle: 'force',
})
// Registry script bundling using scriptOptions
useScriptGoogleAnalytics({
id: 'GA_MEASUREMENT_ID',
scriptOptions: {
bundle: true
}
})
// bundle without cache
useScriptGoogleAnalytics({
id: 'GA_MEASUREMENT_ID',
scriptOptions: {
bundle: 'force'
}
})
Bundle Options
The bundle option accepts the following values:
false- Do not bundle the script (default)true- Bundle the script and use cached version if available'force'- Bundle the script and force download, bypassing cache
Note: Using 'force' will re-download scripts on every build, which may increase build time and provide less security.
Global Bundling
Adjust the default behavior for all scripts using the Nuxt Config. This example sets Nuxt to bundle all scripts by default.
export default defineNuxtConfig({
scripts: {
defaultScriptOptions: {
bundle: true,
}
}
})
Build-time vs Runtime Behavior
Understanding when bundling happens and how it affects runtime behavior is crucial for effective usage.
Build-time Processing
Bundling occurs during the build phase through static code analysis:
// ✅ Nuxt bundles at build-time (static values)
useScript('https://example.com/script.js', { bundle: true })
// ❌ Nuxt cannot bundle (dynamic values)
const scriptUrl = computed(() => getScriptUrl())
useScript(scriptUrl, { bundle: dynamic.value })
Runtime Behavior
At runtime, bundled scripts behave differently:
// Original code
useScript('https://example.com/script.js', { bundle: true })
// After build transformation
useScript('/_scripts/abc123.js', {})
Important: Once Nuxt bundles the script, you lose access to the original URL at runtime. If you need the original URL for tracking or analytics, store it separately.
Static URL Requirements
For bundling to work, the transformer requires static values:
// Static string literals
useScript('https://cdn.example.com/lib.js', { bundle: true })
// Static template literals (no variables)
useScript(`https://cdn.example.com/lib.js`, { bundle: true })
// Constants defined at module level
const SCRIPT_URL = 'https://cdn.example.com/lib.js'
useScript(SCRIPT_URL, { bundle: true })
// Runtime variables
const url = getScriptUrl()
useScript(url, { bundle: true })
// Computed values
const scriptUrl = computed(() => `https://cdn.example.com/${version.value}.js`)
useScript(scriptUrl, { bundle: true })
// Environment variables at runtime
useScript(process.env.SCRIPT_URL, { bundle: true })
// Props or reactive values
useScript(props.scriptUrl, { bundle: true })
Manual Injection Patterns
When automatic bundling isn't possible, you can manually inject bundled scripts:
// 1. Bundle during build with static URL
const staticScript = useScript('https://cdn.example.com/static.js', {
bundle: true,
trigger: 'manual' // Don't auto-load
})
// 2. Conditionally load based on runtime logic
function loadScript() {
if (shouldLoadScript.value) {
staticScript.load()
}
}
// 3. Alternative: Use multiple static configurations
const scriptVariants = {
dev: useScript('https://cdn.example.com/dev.js', { bundle: true, trigger: 'manual' }),
prod: useScript('https://cdn.example.com/prod.js', { bundle: true, trigger: 'manual' })
}
// Load appropriate variant
const currentScript = computed(() =>
isDev ? scriptVariants.dev : scriptVariants.prod
)
Working with Dynamic URLs
For truly dynamic scenarios, consider these patterns:
// Option 1: Pre-bundle known variants
const analytics = {
google: useScript('https://www.googletagmanager.com/gtag/js', { bundle: true }),
plausible: useScript('https://plausible.io/js/script.js', { bundle: true })
}
// Option 2: Fallback to runtime loading
function loadDynamicScript(url: string) {
// Nuxt won't bundle this, but it will work at runtime
return useScript(url, {
bundle: false, // Explicitly disable
trigger: 'manual'
})
}
// Option 3: Use server-side bundling
// Store script content in your bundle and inject manually
const { $script } = useNuxtApp()
$script.add({
innerHTML: await $fetch('/api/dynamic-script-content'),
})
Asset Configuration
Use the assets option in your configuration to customize how Nuxt bundles and caches scripts.
export default defineNuxtConfig({
scripts: {
assets: {
prefix: '/_custom-script-path/',
cacheMaxAge: 86400000, // 1 day in milliseconds
integrity: true, // Enable SRI hash generation
}
}
})
Available Options
prefix- Custom path where Nuxt serves bundled scripts (default:/_scripts/)cacheMaxAge- Cache duration for bundled scripts in milliseconds (default: 7 days)integrity- Enable automatic SRI (Subresource Integrity) hash generation (default:false)
Cache Behavior
The bundling system uses two different cache strategies:
- Build-time cache: Controlled by
cacheMaxAge(default: 7 days). Nuxt re-downloads scripts older than this during builds to ensure freshness. - Runtime cache: Nuxt serves bundled scripts with 1-year cache headers since they are content-addressed by hash.
This dual approach ensures both build performance and reliable browser caching.
Subresource Integrity (SRI)
Subresource Integrity (SRI) is a security feature that ensures scripts haven't been tampered with. According to the W3C SRI specification, it allows browsers to verify that resources fetched are delivered without unexpected manipulation. When enabled, Nuxt calculates a cryptographic hash for each bundled script and adds it as an integrity attribute.
Enabling SRI
export default defineNuxtConfig({
scripts: {
assets: {
integrity: true, // Uses sha384 by default
}
}
})
Hash Algorithms
You can specify the hash algorithm:
export default defineNuxtConfig({
scripts: {
assets: {
integrity: 'sha384', // Default, recommended balance of security/size
// integrity: 'sha256', // Smaller hash
// integrity: 'sha512', // Strongest security
}
}
})
How It Works
When you enable integrity:
- During build, Nuxt hashes each bundled script's content
- Nuxt stores the hash in the build cache for reuse
- Nuxt injects the
integrityattribute into the script tag - Nuxt automatically adds the
crossorigin="anonymous"attribute (required by browsers for SRI)
<!-- Output with integrity enabled -->
<script src="/_scripts/abc123.js"
integrity="sha384-oqVuAfXRKap..."
crossorigin="anonymous"></script>
Security Benefits
- Tamper detection: Browser refuses to execute scripts if the hash doesn't match
- CDN compromise protection: Even if your CDN is compromised, modified scripts won't execute
- Build-time verification: Nuxt calculates the hash from the actual downloaded content