@herazgo/varykit-nuxt

Nuxt 4 module for VaryKit: file-based page testing plus SSR-safe element-level targeting.
1,549
Install
npm install @herazgo/varykit-nuxt
Latest Version:0.3.2
Vue:3
License:MIT
Last Updated:Sep 24, 2026
Links: npm
Maintainer: omidamraei

@herazgo/varykit-nuxt

Nuxt 4 module for VaryKit: file-based page testing plus SSR-safe element-level targeting.

This module depends on @herazgo/varykit-core as a real dependency — you never need to install it separately. For plain Vue (no Nuxt), use @herazgo/varykit-vue, which has the same API shape with an app-level createVaryKit() provider.

The experiment model

Tests are organized as named experiments. Each experiment defines its own segment universe and gets its own cookie (vary:<experiment>), so assignments are independent: a visitor can be hero: b and pricing: control at the same time. One experiment is designated as the router experiment; its segments drive file-based page testing.

Install

pnpm add @herazgo/varykit-nuxt

Setup

Add @herazgo/varykit-nuxt to the modules array of your nuxt.config.ts:

export default defineNuxtConfig({
  modules: ['@herazgo/varykit-nuxt'],

  varykit: {
    experiments: {
      landing: {
        segments: { a: 0.2, b: 0.5 }
      },
      hero: {
        segments: { a: 0.5, b: 0.5 },
        endWith: 'b',
        endsAt: '2026-10-14T00:00:00Z'
      }
    },
    routerExperiment: 'landing'
  }
})

File-based page testing

Create variant files using the __variant naming convention (flat or folder-based):

pages/
  contact.__a.vue
  contact.__b.vue
  contact.__c.vue

All variants collapse into one route (/contact). The visitor's segment in the router experiment is used directly as the variant suffix, so with the config above (landing: { a, b }) only contact.__a.vue and contact.__b.vue are served — contact.__c.vue is never shown because there is no c segment in landing.

All page tests share the router experiment: it's the one knob driving page variants. Element-level experiments are independent (see below), so they don't affect which page variant is served.

Element-level targeting

@herazgo/varykit-nuxt provides useVary, <VaryShow> and <VaryHide> (auto-imported) with the cookie mechanism swapped to Nuxt's SSR-safe useCookie so element targeting is also SSR-consistent (no flash of the wrong variant).

Experiments and segments are defined in config, and both the composable and the components take the experiment name:

<template>
  <VaryShow experiment="hero" segment="a">
    <p>A content</p>
  </VaryShow>

  <VaryShow experiment="hero" segment="b">
    <p>B content</p>
  </VaryShow>

  <!-- inverse: shown for every segment except "b" -->
  <VaryHide experiment="hero" segment="b">
    <p>Shown unless the visitor is in b</p>
  </VaryHide>
</template>
<script setup>
const { segment } = useVary('hero')
</script>

Segments always resolve from the experiment's config entry — there is no per-use override. Each experiment has its own cookie holding its own segment universe; redefining it per call would reassign the visitor and corrupt cross-page tests. Config is validated at build time: routerExperiment must reference a defined experiment, and variant files require routerExperiment to be set.

Exposure events

The module emits a varykit:exposure Nuxt app hook whenever a variant actually renders: the resolver serving a variant page, or a matching <VaryShow> / <VaryHide> slot rendering. Payload: { experiment, segment }.

Wire your analytics in a client plugin:

// plugins/varykit.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.hooks.hook('varykit:exposure', ({ experiment, segment }) => {
    $analytics.track('experiment_viewed', { experiment, segment })
  })
})

Listeners live in client-only plugins, so SSR responses and crawlers without JS produce no exposure events. There is no built-in dedup — count distinct users in your analytics tool.

On pages without variant files (manual branching via useVary), nothing emits automatically — a read is not exposure. When you decide content was seen, emit explicitly:

<script setup>
const { segment } = useVary('landing')

onMounted(() => {
  emitExposure('landing', segment.value)
})
</script>

emitExposure(experiment, segment) is auto-imported, fires the same hook, and is a no-op on the server. Pass the resolved segment from useVary, and fire per instance — not in loops or watchers that re-run.

For conversions, attach the segment to your own events:

<script setup>
const { segment } = useVary('hero')
// e.g. $analytics.track('signup', { experiment: 'hero', segment })
</script>

The module also sets data-varykit-<experiment>="<segment>" on <html>, usable from GTM triggers or CSS selectors.

Typed experiment and segment names

The module generates a type augmentation from your actual varykit config, so typos are compile errors — no extra setup:

<VaryShow experiment="hero" segment="typo">
  <!-- vue-tsc: Type '"typo"' is not assignable to type '"a" | "b"' -->
</VaryShow>

The generated file lives at .nuxt/types/varykit.d.ts. useVary('hero') returns a segment narrowed to the experiment's segment union, and unknown experiment names are compile errors. Works when your app runs vue-tsc/nuxt typecheck.

Lifecycle

Experiments support startsAt (default: immediate), endsAt (default: forever) and endWith — the terminal segment everyone gets outside the window:

Config Behavior
no endWith runs forever (until removed from config)
endWith only pinned immediately at deploy
endWith + endsAt runs until the date, then pinned
endsAt without endWith build error

The pin overrides existing sticky cookies and rewrites them on the next request, so "ended" means everyone — including previously assigned visitors — resolves to the declared segment. Exposure keeps firing with the pinned segment, so post-period conversions attribute cleanly. Lifecycle is evaluated at resolution time (once per request); endWith must name a defined segment, and dates must parse as ISO.

Cookies are always persistent: Max-Age of 400 days (the browser cap), refreshed on every resolution — a visitor who keeps returning never loses their segment. There are no cookie-duration knobs; an assignment lasts as long as the experiment does.

Configuration

experiments

Map of experiment name to its definition:

Option Type Description
segments Record<string, number> Segment weights between 0 and 1. Omitted segments auto-split the remainder.
startsAt string ISO date; the experiment accepts traffic from this moment. Default: immediate.
endsAt string ISO date; the experiment stops splitting traffic at this moment. Requires endWith.
endWith string Terminal segment everyone gets outside the [startsAt, endsAt] window.

routerExperiment

Name of the experiment that drives file-based page variants. Its segment name is matched against the __variant file suffix. Required when variant pages exist.

License

MIT

Related Packages

@herazgo/varykit-vue

Vue 3 composable and component for VaryKit element-level multivariate testing.

1,171
nuxt-ab-testing

Nuxt module designed to seamlessly integrate A/B testing

2,902 18
@privyid/nuxt-csrf

Module CSRF for NUXT

26,088 9
mixpanel-nuxt

Mixpanel module for Nuxt

19,992 7
nuxt-radash

radash for Nuxt

10,601 20

Version History

Version Released License
0.3.2 MIT
0.3.1 MIT
0.3.0 MIT
0.2.3 MIT
0.2.2 MIT