From 1754194ce707f8f414104db744fb2ba8a7a246db Mon Sep 17 00:00:00 2001 From: Dennis Date: Tue, 1 Oct 2024 10:20:28 +0200 Subject: [PATCH 1/3] feat: add Reddit pixel --- playground/nuxt.config.ts | 2 +- playground/pages/index.vue | 4 ++ .../third-parties/reddit-pixel/default.vue | 22 +++++++ .../reddit-pixel/nuxt-scripts.vue | 27 +++++++++ src/registry.ts | 10 ++++ src/runtime/registry/reddit-pixel.ts | 57 +++++++++++++++++++ 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 playground/pages/third-parties/reddit-pixel/default.vue create mode 100644 playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue create mode 100644 src/runtime/registry/reddit-pixel.ts diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index db980ebb..edf3e63f 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -16,4 +16,4 @@ export default defineNuxtConfig({ failOnError: false, }, }, -}) +}) \ No newline at end of file diff --git a/playground/pages/index.vue b/playground/pages/index.vue index 18dbd0b0..8715ca6f 100644 --- a/playground/pages/index.vue +++ b/playground/pages/index.vue @@ -26,6 +26,10 @@ const thirdParties = [ name: 'X Pixel', path: '/third-parties/x-pixel/nuxt-scripts', }, + { + name: 'Reddit Pixel', + path: '/third-parties/reddit-pixel/nuxt-scripts', + }, { name: 'Google Adsense', path: '/third-parties/google-adsense/nuxt-scripts', diff --git a/playground/pages/third-parties/reddit-pixel/default.vue b/playground/pages/third-parties/reddit-pixel/default.vue new file mode 100644 index 00000000..5d2f1a58 --- /dev/null +++ b/playground/pages/third-parties/reddit-pixel/default.vue @@ -0,0 +1,22 @@ + + + diff --git a/playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue b/playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue new file mode 100644 index 00000000..ac5c7c3e --- /dev/null +++ b/playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue @@ -0,0 +1,27 @@ + + + diff --git a/src/registry.ts b/src/registry.ts index 16ebea4d..1f21467b 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -96,6 +96,16 @@ export const registry: (resolve?: (s: string) => string) => RegistryScripts = (r from: resolve('./runtime/registry/x-pixel'), }, }, + { + label: 'Reddit Pixel', + src: 'https://www.redditstatic.com/ads/pixel.js', + category: 'tracking', + logo: ` `, + import: { + name: 'useScriptRedditPixel', + from: resolve('./runtime/registry/reddit-pixel'), + }, + }, // ads { label: 'Google Adsense', diff --git a/src/runtime/registry/reddit-pixel.ts b/src/runtime/registry/reddit-pixel.ts new file mode 100644 index 00000000..3618a169 --- /dev/null +++ b/src/runtime/registry/reddit-pixel.ts @@ -0,0 +1,57 @@ +import type { UseScriptInput } from '@unhead/vue' +import { useRegistryScript } from '../utils' +import { object, string } from '#nuxt-scripts-validator' +import type { RegistryScriptInput } from '#nuxt-scripts' + +type RdtFns = + & ((event: 'init', id: string) => void) + & ((event: 'track', eventName: string) => void) + +export interface RedditPixelApi { + rdt: RdtFns & { + sendEvent: any + callQueue: any + } +} + +declare global { + interface Window extends RedditPixelApi {} +} + +export const RedditPixelOptions = object({ + id: string(), +}) +export type RedditPixelInput = RegistryScriptInput + +export function useScriptRedditPixel(_options?: RedditPixelInput) { + return useRegistryScript('redditPixel', (options) => { + return ({ + scriptInput: { + src: 'https://www.redditstatic.com/ads/pixel.js', + async: true, + } as UseScriptInput, + clientInit: import.meta.server + ? undefined + : () => { + // @ts-expect-error untyped + const rdt: RedditPixelApi['rdt'] = window.rdt = function (...args) { + if (rdt.sendEvent) { + rdt.sendEvent(rdt, args) + } + else { + rdt.callQueue.push(args) + } + } + rdt.callQueue = [] + rdt('init', options?.id) + rdt('track', 'PageVisit') + }, + // No schema needed as script doesn't require specific configuration + scriptOptions: { + use() { + return { rdt: window.rdt } + }, + }, + }) + }, _options) +} From c0b73761a4c201e6e069350e1111041d5f322607 Mon Sep 17 00:00:00 2001 From: Dennis Date: Tue, 1 Oct 2024 10:23:38 +0200 Subject: [PATCH 2/3] fix: remove private pixel id --- playground/pages/third-parties/reddit-pixel/default.vue | 2 +- playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/playground/pages/third-parties/reddit-pixel/default.vue b/playground/pages/third-parties/reddit-pixel/default.vue index 5d2f1a58..e2be96cf 100644 --- a/playground/pages/third-parties/reddit-pixel/default.vue +++ b/playground/pages/third-parties/reddit-pixel/default.vue @@ -8,7 +8,7 @@ useHead({ }) function triggerEvent() { - window.rdt('init', 'a2_fr1pptiy8tay') + window.rdt('init', '') window.rdt('track', 'PageVisit') } diff --git a/playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue b/playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue index ac5c7c3e..dd17beb6 100644 --- a/playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue +++ b/playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue @@ -6,7 +6,7 @@ useHead({ }) // composables return the underlying api as a proxy object and the script state -const { status, rdt } = useScriptRedditPixel({ id: 'a2_fr1pptiy8tay' }) +const { status, rdt } = useScriptRedditPixel({ id: '' }) // this will be triggered once the script is ready async function triggerEvent() { rdt('track', 'PageVisit') From d0340df230cceef65db4b73fc8a29e7b06ff15c4 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Fri, 19 Sep 2025 11:52:13 +1000 Subject: [PATCH 3/3] fix: clean up and docs --- docs/content/scripts/tracking/reddit-pixel.md | 145 ++++++++++++++++++ .../third-parties/reddit-pixel/default.vue | 4 +- .../reddit-pixel/nuxt-scripts.vue | 2 +- src/registry.ts | 2 +- src/runtime/registry/reddit-pixel.ts | 30 ++-- src/runtime/types.ts | 2 + 6 files changed, 167 insertions(+), 18 deletions(-) create mode 100644 docs/content/scripts/tracking/reddit-pixel.md diff --git a/docs/content/scripts/tracking/reddit-pixel.md b/docs/content/scripts/tracking/reddit-pixel.md new file mode 100644 index 00000000..979d832f --- /dev/null +++ b/docs/content/scripts/tracking/reddit-pixel.md @@ -0,0 +1,145 @@ +--- +title: Reddit Pixel +description: Use Reddit Pixel in your Nuxt app. +links: +- label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/reddit-pixel.ts + size: xs +--- + +[Reddit Pixel](https://advertising.reddithelp.com/en/categories/custom-audiences-and-conversion-tracking/reddit-pixel) helps you track conversions and build audiences for your Reddit advertising campaigns. + +Nuxt Scripts provides a registry script composable `useScriptRedditPixel` to easily integrate Reddit Pixel in your Nuxt app. + +### Nuxt Config Setup + +The simplest way to load Reddit Pixel globally in your Nuxt App is to use Nuxt config. Alternatively you can directly +use the [useScriptRedditPixel](#useScriptRedditPixel) composable. + +If you don't plan to send custom events you can use the [Environment overrides](https://nuxt.com/docs/getting-started/configuration#environment-overrides) to +disable the script in development. + +::code-group + +```ts [Always enabled] +export default defineNuxtConfig({ + scripts: { + registry: { + redditPixel: { + id: 'YOUR_ID' + } + } + } +}) +``` + +```ts [Production only] +export default defineNuxtConfig({ + $production: { + scripts: { + registry: { + redditPixel: { + id: 'YOUR_ID', + } + } + } + } +}) +``` + +:: + +#### With Environment Variables + +If you prefer to configure your id using environment variables. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + registry: { + redditPixel: true, + } + }, + // you need to provide a runtime config to access the environment variables + runtimeConfig: { + public: { + scripts: { + redditPixel: { + id: '', // NUXT_PUBLIC_SCRIPTS_REDDIT_PIXEL_ID + }, + }, + }, + }, +}) +``` + +```text [.env] +NUXT_PUBLIC_SCRIPTS_REDDIT_PIXEL_ID= +``` + +## useScriptRedditPixel + +The `useScriptRedditPixel` composable lets you have fine-grain control over when and how Reddit Pixel is loaded on your site. + +```ts +const { proxy } = useScriptRedditPixel({ + id: 'YOUR_ID' +}) +// example +proxy.rdt('track', 'Lead') +``` + +Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage. + +### RedditPixelApi + +```ts +export interface RedditPixelApi { + rdt: RdtFns & { + sendEvent: (rdt: RedditPixelApi['rdt'], args: unknown[]) => void + callQueue: unknown[] + } +} +type RdtFns + = & ((event: 'init', id: string) => void) + & ((event: 'track', eventName: string) => void) +``` + +### Config Schema + +You must provide the options when setting up the script for the first time. + +```ts +export const RedditPixelOptions = object({ + id: string(), +}) +``` + +## Example + +Using Reddit Pixel only in production while using `rdt` to send a tracking event. + +::code-group + +```vue [TrackingButton.vue] + + + +``` + +:: \ No newline at end of file diff --git a/playground/pages/third-parties/reddit-pixel/default.vue b/playground/pages/third-parties/reddit-pixel/default.vue index e2be96cf..37e3822e 100644 --- a/playground/pages/third-parties/reddit-pixel/default.vue +++ b/playground/pages/third-parties/reddit-pixel/default.vue @@ -3,12 +3,12 @@ import { useHead } from '#imports' useHead({ script: [ - { innerHTML: `!(function (w, d) { if (!w.rdt) { var p = w.rdt = function () { p.sendEvent ? p.sendEvent.apply(p, arguments) : p.callQueue.push(arguments) } p.callQueue = [] var t = d.createElement('script') t.src = 'https://www.redditstatic.com/ads/pixel.js', t.async = !0 var s = d.getElementsByTagName('script')[0] s.parentNode.insertBefore(t, s) } }(window, document)) rdt('init', 'a2_fr1pptiy8tay') rdt('track', 'PageVisit')` }, + { innerHTML: `!(function (w, d) { if (!w.rdt) { var p = w.rdt = function () { p.sendEvent ? p.sendEvent.apply(p, arguments) : p.callQueue.push(arguments) } p.callQueue = [] var t = d.createElement('script') t.src = 'https://www.redditstatic.com/ads/pixel.js', t.async = !0 var s = d.getElementsByTagName('script')[0] s.parentNode.insertBefore(t, s) } }(window, document)) rdt('init', 'YOUR_PIXEL_ID') rdt('track', 'PageVisit')` }, ], }) function triggerEvent() { - window.rdt('init', '') + window.rdt('init', 'YOUR_PIXEL_ID') window.rdt('track', 'PageVisit') } diff --git a/playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue b/playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue index dd17beb6..65cd8133 100644 --- a/playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue +++ b/playground/pages/third-parties/reddit-pixel/nuxt-scripts.vue @@ -6,7 +6,7 @@ useHead({ }) // composables return the underlying api as a proxy object and the script state -const { status, rdt } = useScriptRedditPixel({ id: '' }) +const { status, rdt } = useScriptRedditPixel({ id: 'YOUR_PIXEL_ID' }) // this will be triggered once the script is ready async function triggerEvent() { rdt('track', 'PageVisit') diff --git a/src/registry.ts b/src/registry.ts index a34eb4a3..7fbcebda 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -128,7 +128,7 @@ export async function registry(resolve?: (path: string, opts?: ResolvePathOption logo: ` `, import: { name: 'useScriptRedditPixel', - from: resolve('./runtime/registry/reddit-pixel'), + from: await resolve('./runtime/registry/reddit-pixel'), }, }, // ads diff --git a/src/runtime/registry/reddit-pixel.ts b/src/runtime/registry/reddit-pixel.ts index 3618a169..3be1bf38 100644 --- a/src/runtime/registry/reddit-pixel.ts +++ b/src/runtime/registry/reddit-pixel.ts @@ -1,16 +1,16 @@ import type { UseScriptInput } from '@unhead/vue' import { useRegistryScript } from '../utils' import { object, string } from '#nuxt-scripts-validator' -import type { RegistryScriptInput } from '#nuxt-scripts' +import type { RegistryScriptInput } from '#nuxt-scripts/types' -type RdtFns = - & ((event: 'init', id: string) => void) +type RdtFns + = & ((event: 'init', id: string) => void) & ((event: 'track', eventName: string) => void) export interface RedditPixelApi { rdt: RdtFns & { - sendEvent: any - callQueue: any + sendEvent: (rdt: RedditPixelApi['rdt'], args: unknown[]) => void + callQueue: unknown[] } } @@ -33,20 +33,22 @@ export function useScriptRedditPixel(_options?: Reddit clientInit: import.meta.server ? undefined : () => { - // @ts-expect-error untyped - const rdt: RedditPixelApi['rdt'] = window.rdt = function (...args) { - if (rdt.sendEvent) { - rdt.sendEvent(rdt, args) + const rdt = function (...args: unknown[]) { + if ((rdt as any).sendEvent) { + (rdt as any).sendEvent(rdt, args) } else { - rdt.callQueue.push(args) + (rdt as any).callQueue.push(args) } + } as RedditPixelApi['rdt'] + ;(rdt as any).callQueue = [] + window.rdt = rdt + if (options?.id) { + rdt('init', options.id) + rdt('track', 'PageVisit') } - rdt.callQueue = [] - rdt('init', options?.id) - rdt('track', 'PageVisit') }, - // No schema needed as script doesn't require specific configuration + schema: import.meta.dev ? RedditPixelOptions : undefined, scriptOptions: { use() { return { rdt: window.rdt } diff --git a/src/runtime/types.ts b/src/runtime/types.ts index 45e2d356..77eb2f63 100644 --- a/src/runtime/types.ts +++ b/src/runtime/types.ts @@ -28,6 +28,7 @@ import type { GoogleAnalyticsInput } from './registry/google-analytics' import type { GoogleTagManagerInput } from './registry/google-tag-manager' import type { UmamiAnalyticsInput } from './registry/umami-analytics' import type { RybbitAnalyticsInput } from './registry/rybbit-analytics' +import type { RedditPixelInput } from './registry/reddit-pixel' import { object } from '#nuxt-scripts-validator' export type WarmupStrategy = false | 'preload' | 'preconnect' | 'dns-prefetch' @@ -141,6 +142,7 @@ export interface ScriptRegistry { intercom?: IntercomInput matomoAnalytics?: MatomoAnalyticsInput rybbitAnalytics?: RybbitAnalyticsInput + redditPixel?: RedditPixelInput segment?: SegmentInput stripe?: StripeInput xPixel?: XPixelInput