在 Vue 3 工程中接入 Tealium 时,直接复制官方提供的 utag.js 脚本片段会带来两个比较明显的副作用:一是脚本加载阻塞首屏渲染,二是单页应用切换路由后页面浏览事件不会自动更新。工程化的目标不是简单引入工具,而是让标签管理像基础设施一样稳定,业务组件只和统一的数据层交互,不感知 Tealium 的全局对象。下文把加载、类型、路由联动和测试四个环节拆开说明。

一、初始化与脚本加载封装
官方推荐在 <head> 中放置同步的 <script> 标签,这种方式对传统多页应用影响可控,但在 Vue 3 的 CSR 架构里,utag.js 会排在应用 bundle 之前执行,导致首屏可交互时间被第三方标签服务拖慢。如果标签服务出现网络抖动,页面甚至会长时间停在白屏状态。因此建议将脚本注入改为运行时异步加载,并增加模块级缓存,避免每次调用都重新创建脚本元素。
下面是一个轻量级的加载器实现,它根据环境变量选择不同的 Tealium profile,并返回 Promise。
const TEALIUM_CONFIG = {
dev: { src: 'https://tags.tiqcdn.com/utag/your-account/your-profile/dev/utag.js' },
prod: { src: 'https://tags.tiqcdn.com/utag/your-account/your-profile/prod/utag.js' }
}
export function loadTealium(env = import.meta.env.VITE_APP_ENV) {
return new Promise((resolve, reject) => {
const config = TEALIUM_CONFIG[env] || TEALIUM_CONFIG.dev
if (window.utag) return resolve(window.utag)
const script = document.createElement('script')
script.src = config.src
script.async = true
script.onload = () => resolve(window.utag)
script.onerror = reject
document.head.appendChild(script)
})
}这里没有把加载结果写入 Vue 插件或全局混入,而是保持一个独立模块。原因是 Tealium 的初始化通常发生在应用挂载之前,但又不能阻塞挂载。可以用 Promise.all 同时等待用户信息接口和 loadTealium,让两者并行,再挂载 Vue 实例。这样即使标签脚本稍慢,也不会影响已经就绪的本地数据。
二、数据层类型与组合式函数
Tealium 的数据层本质上是一个挂在 window 下的对象,官方常用 utag_data 作为全局变量。直接在不同组件里给这个对象赋值,字段名很容易出现 page_name 与 pageName 混用的情况。为了统一约束,先定义 TypeScript 接口,再通过组合式函数暴露 track 和 view。
这样做还有另一个好处:当 Tealium 配置发生变化,比如新增 user_segment 字段时,只需要在接口中增加一个属性,所有调用点都会获得编译期提示,而不是等到报表里看不到数据才返工排查。
declare global {
interface Window {
utag?: {
link: (data: Record<string, unknown>) => void
view: (data: Record<string, unknown>) => void
}
utag_data?: Record<string, unknown>
}
}
export interface UTagData {
page_name?: string
page_type?: string
user_id?: string
event_name?: string
[key: string]: unknown
}
const dataLayer: UTagData = {}
export function useTealium() {
function push(data: UTagData) {
const merged = { ...dataLayer, ...data }
Object.assign(dataLayer, merged)
window.utag_data = merged
if (window.utag) {
window.utag.link(merged)
}
}
function track(eventName: string, attrs: UTagData = {}) {
push({ ...attrs, event_name: eventName })
}
function view(page: UTagData) {
push({ ...page, page_type: page.page_type || 'spa' })
}
return { track, view, dataLayer }
}类型约束不仅限于字段名,还可以限制事件动作。例如 track 强制要求传入 event_name,view 强制要求 page_type,避免页面浏览和点击事件混用一个通道。组合式函数内部维护一份本地 dataLayer,每次推送时都与已有数据合并,这样可以保留用户 ID、会话 ID 等基础字段,不要求每个事件都重复携带。
三、路由切换中的页面追踪
Vue Router 的 afterEach 钩子是触发虚拟页面浏览的合适位置。每次路由跳转成功后再发送 view 事件,能够保证 fullPath、查询参数、页面标题都已经是最新值。不要在组件的 onMounted 里做这件事,因为组件挂载和路由更新并不完全同步,嵌套路由或异步组件可能导致事件丢失或重复。
import router from './router'
import { useTealium } from './services/tealium'
let lastPath = ''
router.afterEach((to) => {
if (to.fullPath === lastPath) return
lastPath = to.fullPath
const { view } = useTealium()
view({
page_name: to.meta.pageName || to.name,
page_type: 'spa',
url: window.location.origin + to.fullPath,
title: document.title
})
})在路由配置里可以直接把埋点字段放在 meta 中,例如 meta: { pageName: '仪表盘' }。这样路由文件同时承担了导航和埋点声明职责,排查线上数据时不用翻遍组件代码。对于需要额外参数的页面,还可以在 afterEach 中读取 to.query,但要注意先过滤敏感参数,不要把 token、手机号或内部 ID 原样发给第三方平台。
为了避免返回同一路径时重复发送,可以用 lastPath 做一次简单去重。真实项目还可以结合 beforeEach 记录进入时间,在发送事件时带上 previous_page,这比依赖 Tealium 自动推断更可靠。
四、环境隔离、调试与测试
开发环境和生产环境建议使用不同的 Tealium profile 或 environment。Vite 项目可以通过 import.meta.env 注入 VITE_APP_ENV,加载器根据它选择 dev 或 prod 的脚本地址。测试环境中甚至可以不加载真实脚本,而是用 mock 对象验证事件是否按预期发送。
单元测试的重点不是 Tealium 的报表结果,而是我们自己的封装层是否正确合并数据、是否调用 window.utag.link。下面这段 vitest 用例展示了最小 mock 方式。
import { beforeEach, vi, expect } from 'vitest'
import { useTealium } from './services/tealium'
beforeEach(() => {
window.utag_data = {}
window.utag = {
link: vi.fn(),
view: vi.fn()
}
})
test('track merges data and calls utag.link', () => {
const { track } = useTealium()
track('trial_start', { source: 'nav' })
expect(window.utag.link).toHaveBeenCalledWith(
expect.objectContaining({
event_name: 'trial_start',
source: 'nav'
})
)
})调试阶段可以增加一个开关:当 import.meta.env.DEV 为真时,所有 push 操作同时通过 console.debug 输出,这样既能快速核对数据结构,又不会在浏览器控制台里刷出过多噪音。发布前再把开关关闭,或仅对特定 IP 开放。
工程化 Tealium 的过程,本质上是在第三方标签服务和 Vue 3 响应式系统之间建立一层稳定的适配层。脚本加载、类型约束、路由联动和测试这四个环节并不复杂,但它们共同决定了后续数据采集的可维护性。把 Tealium 当成基础设施来对待,而不是散落在组件里的调用代码,才能让标签管理不拖慢首屏,也不污染报表。