From f8de285cdb48498473f981bf10da7a21b581bece Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 14 Oct 2025 15:41:19 +1100 Subject: [PATCH 1/2] feat: plausible script updates --- .../scripts/analytics/plausible-analytics.md | 77 +++++++- playground/pages/index.vue | 6 + .../third-parties/plausible-analytics-v2.vue | 73 +++++++ .../third-parties/plausible-analytics.vue | 9 +- src/runtime/registry/plausible-analytics.ts | 181 ++++++++++++++++-- 5 files changed, 319 insertions(+), 27 deletions(-) create mode 100644 playground/pages/third-parties/plausible-analytics-v2.vue diff --git a/docs/content/scripts/analytics/plausible-analytics.md b/docs/content/scripts/analytics/plausible-analytics.md index d17e65c4..6541be27 100644 --- a/docs/content/scripts/analytics/plausible-analytics.md +++ b/docs/content/scripts/analytics/plausible-analytics.md @@ -25,7 +25,10 @@ export default defineNuxtConfig({ scripts: { registry: { plausibleAnalytics: { - domain: 'YOUR_DOMAIN' + // Get this from your Plausible script URL: + // https://plausible.io/js/pa-gYyxvZhkMzdzXBAtSeSNz.js + // ^^^^^^^^^^^^^^^^^^^^^^^^^^ + scriptId: 'gYyxvZhkMzdzXBAtSeSNz' } } } @@ -38,7 +41,7 @@ export default defineNuxtConfig({ scripts: { registry: { plausibleAnalytics: { - domain: 'YOUR_DOMAIN', + scriptId: 'YOUR_SCRIPT_ID', } } } @@ -59,8 +62,8 @@ export default defineNuxtConfig({ scripts: { plausibleAnalytics: { // .env - // NUXT_PUBLIC_SCRIPTS_PLAUSIBLE_ANALYTICS_DOMAIN= - domain: '' + // NUXT_PUBLIC_SCRIPTS_PLAUSIBLE_ANALYTICS_SCRIPT_ID= + scriptId: '' }, }, }, @@ -75,8 +78,11 @@ export default defineNuxtConfig({ The `useScriptPlausibleAnalytics` composable lets you have fine-grain control over when and how Plausible Analytics is loaded on your site. ```ts +// New October 2025 format const plausible = useScriptPlausibleAnalytics({ - domain: 'YOUR_DOMAIN' + // Extract from: https://plausible.io/js/pa-gYyxvZhkMzdzXBAtSeSNz.js + // ^^^^^^^^^^^^^^^^^^^^^^^^^^ + scriptId: 'gYyxvZhkMzdzXBAtSeSNz' }) ``` @@ -112,10 +118,63 @@ export interface PlausibleAnalyticsApi { You must provide the options when setting up the script for the first time. ```ts -export const PlausibleAnalyticsOptions = object({ - domain: string(), // required - extension: optional(union([union(extensions), array(union(extensions))])), -}) +export interface PlausibleAnalyticsOptions { + /** + * Unique script ID for your site (recommended - new format as of October 2025) + * Extract from: + */ + scriptId?: string + /** Custom properties to track with every pageview */ + customProperties?: Record + /** Custom tracking endpoint URL */ + endpoint?: string + /** Configure file download tracking */ + fileDownloads?: { + fileExtensions?: string[] + } + /** Enable hash-based routing for single-page apps */ + hashBasedRouting?: boolean + /** Set to false to manually trigger pageviews */ + autoCapturePageviews?: boolean + /** Enable tracking on localhost */ + captureOnLocalhost?: boolean + /** Enable form submission tracking */ + trackForms?: boolean +} +``` + +```ts +export interface PlausibleAnalyticsDeprecatedOptions { + /** + * Your site domain + * @deprecated Use `scriptId` instead (new October 2025 format) + */ + domain?: string + /** + * Script extensions for additional features + * @deprecated Use init options like `hashBasedRouting`, `captureOnLocalhost`, etc. instead + */ + extension?: 'hash' | 'outbound-links' | 'file-downloads' | 'tagged-events' | 'revenue' | 'pageview-props' | 'compat' | 'local' | 'manual' +} + +``` + +**Note:** The `scriptId` is found in your Plausible dashboard under **Site Installation** in your site settings. + +**Extracting your Script ID:** + +Plausible provides you with a script tag like this: + +```html + +``` + +Your `scriptId` is the part after `pa-` and before `.js`: + +```ts +scriptId: 'gYyxvZhkMzdzXBAtSeSNz' +// ^^^^^^^^^^^^^^^^^^^^^^^ +// Extract from: pa-{scriptId}.js ``` ## Example diff --git a/playground/pages/index.vue b/playground/pages/index.vue index e8d9512d..6deb1828 100644 --- a/playground/pages/index.vue +++ b/playground/pages/index.vue @@ -71,6 +71,12 @@ const analytics = registryScripts logo: registryScripts.find(s => s.label === 'Google Analytics')?.logo, registryScript: null, }, + { + name: 'Plausible Analytics v2 (Oct 2025)', + path: '/third-parties/plausible-analytics-v2', + logo: registryScripts.find(s => s.label === 'Plausible Analytics')?.logo, + registryScript: null, + }, ]) const pixels = registryScripts diff --git a/playground/pages/third-parties/plausible-analytics-v2.vue b/playground/pages/third-parties/plausible-analytics-v2.vue new file mode 100644 index 00000000..0b582db0 --- /dev/null +++ b/playground/pages/third-parties/plausible-analytics-v2.vue @@ -0,0 +1,73 @@ + + + diff --git a/playground/pages/third-parties/plausible-analytics.vue b/playground/pages/third-parties/plausible-analytics.vue index 4a91d650..cde13feb 100644 --- a/playground/pages/third-parties/plausible-analytics.vue +++ b/playground/pages/third-parties/plausible-analytics.vue @@ -6,13 +6,20 @@ useHead({ }) // composables return the underlying api as a proxy object and the script state +// Using legacy domain format for playground - in production use scriptId instead const { status, proxy } = useScriptPlausibleAnalytics({ domain: 'scripts.nuxt.com', - extension: 'local', + captureOnLocalhost: true, // New October 2025 init option scriptOptions: { trigger: 'onNuxtReady', }, }) +// Example with new scriptId format: +// const { status, proxy } = useScriptPlausibleAnalytics({ +// scriptId: 'YOUR_SCRIPT_ID', // Get from Plausible dashboard +// captureOnLocalhost: true, +// scriptOptions: { trigger: 'onNuxtReady' }, +// }) const clicks = ref(0) diff --git a/src/runtime/registry/plausible-analytics.ts b/src/runtime/registry/plausible-analytics.ts index 1bfc01eb..50d41653 100644 --- a/src/runtime/registry/plausible-analytics.ts +++ b/src/runtime/registry/plausible-analytics.ts @@ -1,7 +1,9 @@ import { useRegistryScript } from '../utils' -import { array, literal, object, optional, string, union } from '#nuxt-scripts-validator' +import { any, array, boolean, literal, object, optional, record, string, union } from '#nuxt-scripts-validator' import type { RegistryScriptInput } from '#nuxt-scripts/types' +import { logger } from '../logger' +// Legacy extensions (deprecated but kept for backward compatibility) const extensions = [ literal('hash'), literal('outbound-links'), @@ -14,40 +16,185 @@ const extensions = [ literal('manual'), ] -export const PlausibleAnalyticsOptions = object({ - domain: string(), // required +const PlausibleAnalyticsOptionsSchema = object({ + // New October 2025: unique script ID per site (replaces domain) + scriptId: optional(string()), + // Legacy: domain-based approach (deprecated) + domain: optional(string()), + // Legacy extension support (deprecated) extension: optional(union([union(extensions), array(union(extensions))])), + // New October 2025 init options + customProperties: optional(record(string(), any())), + endpoint: optional(string()), + fileDownloads: optional(object({ + fileExtensions: optional(array(string())), + })), + hashBasedRouting: optional(boolean()), + autoCapturePageviews: optional(boolean()), + captureOnLocalhost: optional(boolean()), + trackForms: optional(boolean()), }) -export type PlausibleAnalyticsInput = RegistryScriptInput +/** + * Plausible Analytics options + * @see https://plausible.io/docs/script-extensions + */ +export interface PlausibleAnalyticsOptions { + /** + * Unique script ID for your site (recommended - new format as of October 2025) + * Get this from your Plausible dashboard under Site Installation + * + * Extract it from your Plausible script URL: + * ``` + * + * ^^^^^^^^^^^^^^^^^^^^^^^^^^ + * scriptId: 'gYyxvZhkMzdzXBAtSeSNz' + * ``` + * @example 'gYyxvZhkMzdzXBAtSeSNz' + */ + scriptId?: string + /** + * Your site domain + * @deprecated Use `scriptId` instead (new October 2025 format) + * @example 'example.com' + */ + domain?: string + /** + * Script extensions for additional features + * @deprecated Use init options like `hashBasedRouting`, `captureOnLocalhost`, etc. instead (new October 2025 format) + */ + extension?: 'hash' | 'outbound-links' | 'file-downloads' | 'tagged-events' | 'revenue' | 'pageview-props' | 'compat' | 'local' | 'manual' | Array<'hash' | 'outbound-links' | 'file-downloads' | 'tagged-events' | 'revenue' | 'pageview-props' | 'compat' | 'local' | 'manual'> + /** Custom properties to track with every pageview */ + customProperties?: Record + /** Custom tracking endpoint URL */ + endpoint?: string + /** Configure file download tracking */ + fileDownloads?: { + /** File extensions to track (default: pdf, xlsx, docx, txt, rtf, csv, exe, key, pps, ppt, pptx, 7z, pkg, rar, gz, zip, avi, mov, mp4, mpeg, wmv, midi, mp3, wav, wma, dmg) */ + fileExtensions?: string[] + } + /** Enable hash-based routing for single-page apps */ + hashBasedRouting?: boolean + /** Set to false to manually trigger pageviews */ + autoCapturePageviews?: boolean + /** Enable tracking on localhost */ + captureOnLocalhost?: boolean + /** Enable form submission tracking */ + trackForms?: boolean +} + +export type PlausibleAnalyticsInput = RegistryScriptInput + +/** + * Init options for plausible.init() (October 2025 format) + * @see https://plausible.io/docs/script-extensions + */ +export interface PlausibleInitOptions { + customProperties?: Record + endpoint?: string + fileDownloads?: { + fileExtensions?: string[] + } + hashBasedRouting?: boolean + autoCapturePageviews?: boolean + captureOnLocalhost?: boolean +} + +export type PlausibleFunction = ((event: '404', options: Record) => void) + & ((event: 'event', options: Record) => void) + & ((...params: any[]) => void) & { + q: any[] + init: (options: PlausibleInitOptions) => void + } export interface PlausibleAnalyticsApi { - plausible: ((event: '404', options: Record) => void) - & ((event: 'event', options: Record) => void) - & ((...params: any[]) => void) & { - q: any[] - } + plausible: PlausibleFunction } declare global { interface Window { - plausible: PlausibleAnalyticsApi + plausible: PlausibleFunction } } export function useScriptPlausibleAnalytics(_options?: PlausibleAnalyticsInput) { - return useRegistryScript('plausibleAnalytics', (options) => { - const extensions = Array.isArray(options?.extension) ? options.extension.join('.') : [options?.extension] + return useRegistryScript('plausibleAnalytics', (options) => { + // Determine which script format to use + const useNewScript = !!options?.scriptId + const useLegacyScript = !!options?.extension + + // Validate: don't mix deprecated and new options + if (import.meta.dev) { + // Check for missing required options + if (!useNewScript && !options?.domain) { + logger.warn('Plausible Analytics: No `scriptId` or `domain` provided. Please provide either `scriptId` (recommended, new October 2025 format) or `domain` (legacy).') + } + + // Check for mixing new and deprecated options + if (useNewScript && options?.domain) { + logger.warn('Plausible Analytics: You are using both `scriptId` (new format) and `domain` (deprecated). Please use only `scriptId` for the new October 2025 format.') + } + if (useNewScript && useLegacyScript) { + logger.warn('Plausible Analytics: You are using both `scriptId` (new format) and `extension` (deprecated). Please use `scriptId` with init options like `hashBasedRouting`, `captureOnLocalhost`, etc. instead.') + } + + // Deprecation warnings + if (!useNewScript && options?.domain && !useLegacyScript) { + logger.warn('Plausible Analytics: You are using `domain` which is deprecated. Consider migrating to the new October 2025 format using `scriptId` (get it from your Plausible dashboard).') + } + if (useLegacyScript) { + logger.warn('Plausible Analytics: You are using `extension` which is deprecated. Consider migrating to the new October 2025 format using `scriptId` with init options like `hashBasedRouting`, `captureOnLocalhost`, etc.') + } + } + + // Build script URL + let scriptSrc: string + if (useNewScript) { + // New October 2025 format with unique script ID + scriptSrc = `https://plausible.io/js/pa-${options.scriptId}.js` + } + else if (useLegacyScript) { + // Legacy extension format + const extensions = Array.isArray(options.extension) ? options.extension.join('.') : [options.extension] + scriptSrc = `https://plausible.io/js/script.${extensions}.js` + } + else { + // Legacy basic script + scriptSrc = 'https://plausible.io/js/script.js' + } + + // Build init options for new script format + const initOptions: PlausibleInitOptions = {} + if (options?.customProperties) initOptions.customProperties = options.customProperties + if (options?.endpoint) initOptions.endpoint = options.endpoint + if (options?.fileDownloads) initOptions.fileDownloads = options.fileDownloads + if (options?.hashBasedRouting !== undefined) initOptions.hashBasedRouting = options.hashBasedRouting + if (options?.autoCapturePageviews !== undefined) initOptions.autoCapturePageviews = options.autoCapturePageviews + if (options?.captureOnLocalhost !== undefined) initOptions.captureOnLocalhost = options.captureOnLocalhost + + // Build script input + const scriptInput = !useNewScript && options?.domain + ? { + 'src': scriptSrc, + 'data-domain': options.domain, + } + : { + src: scriptSrc, + } + return { - scriptInput: { - 'src': options?.extension ? `https://plausible.io/js/script.${extensions}.js` : 'https://plausible.io/js/script.js', - 'data-domain': options?.domain, - }, - schema: import.meta.dev ? PlausibleAnalyticsOptions : undefined, + scriptInput, + schema: import.meta.dev ? PlausibleAnalyticsOptionsSchema : undefined, scriptOptions: { use() { return { plausible: window.plausible } }, + clientInit() { + // @ts-expect-error untyped + // eslint-disable-next-line @typescript-eslint/no-unused-expressions,@stylistic/max-statements-per-line,prefer-rest-params + window.plausible = window.plausible || function () { (plausible.q = plausible.q || []).push(arguments) }, plausible.init = plausible.init || function (i) { plausible.o = i || {} } + window.plausible.init(initOptions) + }, }, } }, _options) From 4fd4a4b9785ebc645b9abe1e0e1cb69300c4a61c Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Tue, 14 Oct 2025 15:44:37 +1100 Subject: [PATCH 2/2] chore: simplify --- src/runtime/registry/plausible-analytics.ts | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/runtime/registry/plausible-analytics.ts b/src/runtime/registry/plausible-analytics.ts index 50d41653..9a955032 100644 --- a/src/runtime/registry/plausible-analytics.ts +++ b/src/runtime/registry/plausible-analytics.ts @@ -127,24 +127,16 @@ export function useScriptPlausibleAnalytics(_op if (import.meta.dev) { // Check for missing required options if (!useNewScript && !options?.domain) { - logger.warn('Plausible Analytics: No `scriptId` or `domain` provided. Please provide either `scriptId` (recommended, new October 2025 format) or `domain` (legacy).') + logger.warn('Plausible Analytics: No `scriptId` or `domain` provided. Please provide either `scriptId` or `domain` (legacy).') } // Check for mixing new and deprecated options if (useNewScript && options?.domain) { - logger.warn('Plausible Analytics: You are using both `scriptId` (new format) and `domain` (deprecated). Please use only `scriptId` for the new October 2025 format.') + logger.warn('Plausible Analytics: You are using both `scriptId` (new format) and `domain` (deprecated). Please use only `scriptId` for the new format.') } if (useNewScript && useLegacyScript) { logger.warn('Plausible Analytics: You are using both `scriptId` (new format) and `extension` (deprecated). Please use `scriptId` with init options like `hashBasedRouting`, `captureOnLocalhost`, etc. instead.') } - - // Deprecation warnings - if (!useNewScript && options?.domain && !useLegacyScript) { - logger.warn('Plausible Analytics: You are using `domain` which is deprecated. Consider migrating to the new October 2025 format using `scriptId` (get it from your Plausible dashboard).') - } - if (useLegacyScript) { - logger.warn('Plausible Analytics: You are using `extension` which is deprecated. Consider migrating to the new October 2025 format using `scriptId` with init options like `hashBasedRouting`, `captureOnLocalhost`, etc.') - } } // Build script URL