{"_id":"@avsbhq/nuxt","_rev":"2-e1315840924144fe6dc002ebcae8123e","name":"@avsbhq/nuxt","dist-tags":{"latest":"1.0.3"},"versions":{"1.0.2":{"name":"@avsbhq/nuxt","version":"1.0.2","keywords":["avsb","feature-flags","ab-testing","experiments","nuxt","nuxt-module","vue","sdk"],"license":"MIT","_id":"@avsbhq/nuxt@1.0.2","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"homepage":"https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-nuxt#readme","bugs":{"url":"https://github.com/avsbhq/a-vs-b/issues"},"dist":{"shasum":"d5f535986b139336f81f342e834da1ac1dcee913","tarball":"https://registry.npmjs.org/@avsbhq/nuxt/-/nuxt-1.0.2.tgz","fileCount":11,"integrity":"sha512-pv5gddeyuvDxMb1M2cQcGhqV13Wb7K00OOmIeT9TJmvTy5mywTyBiGWXo6BiU98Duz4yAYSDZN489KCtMaVYdw==","signatures":[{"sig":"MEUCIC9MTVTrePjIuaOh5bwaA5TggSfZzuZvcUrFADVxED3oAiEAmxWG7eqeaDTRQmoe6R892tGn01fzjrLfKXmoxvV/tpo=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":35181},"main":"./dist/module.js","type":"module","types":"./dist/module.d.ts","exports":{".":{"types":"./dist/module.d.ts","import":"./dist/module.js"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js"},"./package.json":"./package.json"},"gitHead":"5eed46d5944b522ba0aa34e82850c98bb91667fe","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit"},"_npmUser":{"name":"main1479","email":"m.main2402@gmail.com"},"repository":{"url":"git+https://github.com/avsbhq/a-vs-b.git","type":"git","directory":"packages/avsb-nuxt"},"_npmVersion":"10.9.8","description":"Nuxt module for A vs B feature flags: one line in nuxt.config, auto-imported composables, and a server-side datafile bootstrap so the first paint shows real values.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.3","dependencies":{"@avsbhq/vue":"^1.0.2","@avsbhq/core":"^1.4.0","@avsbhq/browser":"^1.3.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vue":"^3.4.0","tsup":"^8.0.0","vitest":"^4.1.4","@nuxt/kit":"^3.13.0","happy-dom":"^14.0.0","typescript":"^5.5.0","@types/node":"^20.0.0","@nuxt/schema":"^3.13.0"},"peerDependencies":{"nuxt":">=3.7.0","@nuxt/kit":">=3.7.0"},"peerDependenciesMeta":{"nuxt":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/nuxt_1.0.2_1785971349741_0.2465971543240275","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@avsbhq/nuxt","version":"1.0.3","description":"Nuxt module for A vs B feature flags: one line in nuxt.config, auto-imported composables, and a server-side datafile bootstrap so the first paint shows real values.","keywords":["avsb","feature-flags","ab-testing","experiments","nuxt","nuxt-module","vue","sdk"],"license":"MIT","type":"module","sideEffects":false,"main":"./dist/module.js","types":"./dist/module.d.ts","exports":{".":{"types":"./dist/module.d.ts","import":"./dist/module.js"},"./server":{"types":"./dist/server.d.ts","import":"./dist/server.js"},"./package.json":"./package.json"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/avsbhq/a-vs-b.git","directory":"packages/avsb-nuxt"},"homepage":"https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-nuxt#readme","bugs":{"url":"https://github.com/avsbhq/a-vs-b/issues"},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","typecheck":"tsc --noEmit"},"dependencies":{"@avsbhq/browser":"^1.3.1","@avsbhq/core":"^1.4.0","@avsbhq/vue":"^1.0.2"},"peerDependencies":{"@nuxt/kit":">=3.7.0","nuxt":">=3.7.0"},"peerDependenciesMeta":{"nuxt":{"optional":true}},"devDependencies":{"@nuxt/kit":"^3.13.0","@nuxt/schema":"^3.13.0","@types/node":"^20.0.0","happy-dom":"^14.0.0","tsup":"^8.0.0","typescript":"^5.5.0","vitest":"^4.1.4","vue":"^3.4.0"},"_id":"@avsbhq/nuxt@1.0.3","gitHead":"d31d88fbf202f40041b4cb47f77882f454fa5ef8","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-Z9hOQRtEob//Y0eR15vC5Gck6JcW3NezZ1DTdZjd0QJmzaMcvzWYME5SamkwSBQx9BKgPTgCuos87/3O7nLrUg==","shasum":"e95c640f403a1f95ae1dcb8442db526257fa83c2","tarball":"https://registry.npmjs.org/@avsbhq/nuxt/-/nuxt-1.0.3.tgz","fileCount":11,"unpackedSize":35197,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDO9yAoGPe/0/YfnKIgOQoWAWiyeR1ghmsrx5M6N8HjhgIgYmktBIFI4EwLi8mvtJnyOZxdy+hmIEb3R3Ehy+Syl3k="}]},"_npmUser":{"name":"main1479","email":"m.main2402@gmail.com"},"directories":{},"maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nuxt_1.0.3_1786795855252_0.9726420762724268"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-05T23:09:09.580Z","modified":"2026-08-15T12:10:55.548Z","1.0.2":"2026-08-05T23:09:09.911Z","1.0.3":"2026-08-15T12:10:55.393Z"},"bugs":{"url":"https://github.com/avsbhq/a-vs-b/issues"},"license":"MIT","homepage":"https://github.com/avsbhq/a-vs-b/tree/main/packages/avsb-nuxt#readme","keywords":["avsb","feature-flags","ab-testing","experiments","nuxt","nuxt-module","vue","sdk"],"repository":{"type":"git","url":"git+https://github.com/avsbhq/a-vs-b.git","directory":"packages/avsb-nuxt"},"description":"Nuxt module for A vs B feature flags: one line in nuxt.config, auto-imported composables, and a server-side datafile bootstrap so the first paint shows real values.","maintainers":[{"name":"main1479","email":"m.main2402@gmail.com"}],"readme":"# @avsbhq/nuxt\n\nNuxt module for the [A vs B](https://app.avsb.cloud) platform.\n\nOne line in `nuxt.config`, and every component can read feature flags with auto-imported composables. The datafile is fetched while the page renders on the server and travels to the browser in the payload, so the first paint shows real values instead of your defaults.\n\nBuilt on [`@avsbhq/vue`](https://www.npmjs.com/package/@avsbhq/vue), which is built on [`@avsbhq/browser`](https://www.npmjs.com/package/@avsbhq/browser). Anything the Vue SDK can do, this module can do: it installs the same plugin for you and adds the Nuxt half (config, auto-imports, server rendering).\n\n---\n\n## 1. Install\n\n```bash\nnpm install @avsbhq/nuxt\n```\n\nNuxt 3.7 or later is required as a peer dependency. Nuxt 4 is supported.\n\n---\n\n## 2. Quickstart\n\n```ts\n// nuxt.config.ts\n// docs-example: not typechecked here, because `defineNuxtConfig` and the `avsb`\n// config key both come from the types Nuxt generates into a project's .nuxt\n// directory when it registers this module, and no npm package supplies them.\nexport default defineNuxtConfig({\n  modules: ['@avsbhq/nuxt'],\n  avsb: {\n    sdkKey: process.env.AVSB_SDK_KEY,\n  },\n});\n```\n\n```vue\n<!-- components/CheckoutButton.vue -->\n<script setup lang=\"ts\">\n// No import line: the module auto-imports the composables.\nconst newCheckout = useBoolFlag('new-checkout-flow', false);\nconst track = useTrack();\n\n// The visitor is about to see this decision, so record it once.\nuseExposure('new-checkout-flow');\n</script>\n\n<template>\n  <button @click=\"track('checkout_started', { value: 99 })\">\n    {{ newCheckout.value ? 'Start checkout (new)' : 'Buy now' }}\n  </button>\n</template>\n```\n\nThat is the whole setup. There is no provider to mount and no plugin to write.\n\n---\n\n## 3. SDK keys\n\nGet the SDK key for the environment you want from your project: **app.avsb.cloud**, then **Settings**, then **Environments**. The format is `sdk_<environment>_<id>`, for example `sdk_production_ttqm0eaj4vth1krcb2xn`.\n\nThere is one SDK key per environment, and \"SDK key\" is its only name. There is no separate client key and server key to choose between.\n\nYour SDK key is a public identifier, not a secret: it is safe to ship in browser and mobile bundles, it can only fetch that environment's flag configuration and send events, and it can never read or change anything in your dashboard.\n\nBecause the key lives in `runtimeConfig.public`, Nuxt lets you point a built app at another environment without rebuilding it:\n\n```bash\nNUXT_PUBLIC_AVSB_SDK_KEY=sdk_production_ttqm0eaj4vth1krcb2xn node .output/server/index.mjs\n```\n\nThe module reads `avsb.sdkKey` first, then `NUXT_PUBLIC_AVSB_SDK_KEY`, then `AVSB_SDK_KEY`. With none of them set it warns at build time, and at runtime it creates no client and says so once, naming the fix.\n\nThe client also checks the shape of the key when it is constructed. A key that does not look like an SDK key (a pasted dashboard URL, a truncated copy, a personal access token or a service token) logs one actionable error naming what it got and where the real key lives.\n\n---\n\n## 4. What the module does\n\n1. Publishes your settings to `runtimeConfig.public.avsb`.\n2. Auto-imports the `@avsbhq/vue` composables (section 6).\n3. Registers one plugin that runs on both sides:\n   - **Server**: fetches the datafile (once per process per TTL, and once for a burst of concurrent requests), puts it in the payload, and installs the Vue plugin with a client that has no timers, no cache and no network. Flags are answered from that datafile while the page renders.\n   - **Browser**: reads the same datafile out of the payload and installs the Vue plugin with a live client that starts ready. Nothing is fetched before the first paint, and no flag flickers from a default to its real value.\n\n---\n\n## 5. Module options\n\n```ts\nimport type { AvsbClientOptions } from '@avsbhq/browser';\n\ninterface AvsbModuleOptions {\n  /** SDK key. Falls back to NUXT_PUBLIC_AVSB_SDK_KEY, then AVSB_SDK_KEY. */\n  sdkKey?: string;\n  /** Fetch the datafile during server rendering and send it in the payload. Default true. */\n  bootstrap?: boolean;\n  /** Auto-import the composables. Default true. */\n  autoImports?: boolean;\n  /** CDN base for datafile fetches. Default 'https://cdn.avsb.cloud'. */\n  cdnHost?: string;\n  /** Milliseconds the server-side datafile fetch may take. Default 3000. */\n  bootstrapTimeout?: number;\n  /** Milliseconds a server-fetched datafile is reused across requests. Default 60000. */\n  bootstrapMaxAge?: number;\n  /** Everything else AvsbClient accepts that survives JSON. */\n  client?: AvsbClientRuntimeOptions;\n}\n\n/** Derived from AvsbClientOptions, so it cannot drift from the SDK. */\ntype AvsbClientRuntimeOptions = Pick<\n  AvsbClientOptions,\n  | 'pollingInterval'\n  | 'autoRefresh'\n  | 'cdnHost'\n  | 'initTimeout'\n  | 'cache'\n  | 'cacheMaxAgeMs'\n  | 'adoptSnippetVisitorId'\n  | 'pauseWhenHidden'\n  | 'refetchOnFocus'\n  | 'maxPendingEvents'\n  | 'streaming'\n  | 'streamingEndpoint'\n  | 'logLevel'\n>;\n```\n\n`logger` and `onError` are absent from `client` because `runtimeConfig` cannot carry functions. If you need them, build your own client and mount `<AvsbProvider :client=\"client\">` from `@avsbhq/vue` instead of using this module.\n\n```ts\n// nuxt.config.ts\n// docs-example: not typechecked here, because `defineNuxtConfig` and the `avsb`\n// config key both come from the types Nuxt generates into a project's .nuxt\n// directory when it registers this module, and no npm package supplies them.\nexport default defineNuxtConfig({\n  modules: ['@avsbhq/nuxt'],\n  avsb: {\n    sdkKey: process.env.AVSB_SDK_KEY,\n    bootstrapMaxAge: 30_000,\n    client: { pollingInterval: 30_000, logLevel: 'debug' },\n  },\n});\n```\n\nSettings under `avsb.client` never override the SDK key, and never turn polling, caching or streaming back on during a server render.\n\n---\n\n## 6. What gets auto-imported\n\n```\nuseFlag        useFlagValue    useBoolFlag     useStringFlag\nuseNumberFlag  useJsonFlag     useAllFlags     useFlagReady\nuseAvsbStatus  useAvsbClient   useIdentify     useAlias\nuseReset       useTrack        useExposure     useFlagSubscription\n```\n\nEvery one is the composable from `@avsbhq/vue`, with the same signature and the same behaviour. Their reference is the [`@avsbhq/vue` README](https://www.npmjs.com/package/@avsbhq/vue): what a `Flag<T>` carries, why a read in `<script setup>` needs `.value.value`, when an exposure is recorded, and what `useAvsbStatus()` reports.\n\nSet `autoImports: false` and import them yourself:\n\n```ts\nimport { useBoolFlag } from '@avsbhq/vue';\n```\n\n`AvsbProvider` and `AvsbPlugin` are deliberately not auto-imported. The module already installs one, and a second provider inside it would run two clients with two visitor ids.\n\n---\n\n## 7. Server rendering\n\n### The default: the module fetches the datafile\n\nNothing to write. The plugin fetches the datafile on the server, caches it for `bootstrapMaxAge` across requests, and deduplicates concurrent requests so a burst of traffic triggers one fetch. A failed fetch is never cached: the page renders your default values, one warning says why, and the next request tries again.\n\n### If you already run a server client\n\nApps that evaluate flags in Nitro routes with `@avsbhq/node` already hold a datafile. Hand that one over instead of paying for a second fetch:\n\n```ts\n// plugins/avsb-bootstrap.server.ts\nimport { defineNuxtPlugin, useState } from 'nuxt/app';\nimport { AVSB_BOOTSTRAP_STATE_KEY, getAvsbBootstrap } from '@avsbhq/nuxt/server';\nimport type { FlagDatafile } from '@avsbhq/nuxt/server';\nimport { avsbServer } from '../server/avsb';\n\nexport default defineNuxtPlugin(() => {\n  const bootstrap = useState<FlagDatafile | null>(AVSB_BOOTSTRAP_STATE_KEY, () => null);\n  bootstrap.value = getAvsbBootstrap(avsbServer);\n});\n```\n\nThe module's own plugin runs with `enforce: 'post'`, so a plugin like that one has already run and the module leaves the value alone.\n\n`getAvsbBootstrap` is the same helper, with the same name and meaning, that `@avsbhq/svelte/sveltekit` and `@avsbhq/solid/solid-start` ship: it hands back the DATAFILE, which is what the browser client's `bootstrap` option takes. It is JSON-safe by construction, because it is the document the CDN serves.\n\n### `@avsbhq/nuxt/server`\n\n```ts\nimport type { DatafileFetcher } from '@avsbhq/nuxt/server';\n\nconst AVSB_BOOTSTRAP_STATE_KEY: 'avsb:bootstrap';\nconst AVSB_CONTEXT_STATE_KEY: 'avsb:context';\n\ninterface AvsbServerClient {\n  getDatafile(): FlagDatafile | null;\n}\n\nfunction getAvsbBootstrap(serverClient: AvsbServerClient): FlagDatafile | null;\n\ninterface LoadDatafileOptions {\n  cdnHost?: string;\n  /** Milliseconds before the fetch is abandoned. Default 3000. */\n  timeoutMs?: number;\n  /** Milliseconds a fetched datafile is reused across requests. Default 60000. */\n  maxAgeMs?: number;\n  onError?: (error: Error) => void;\n}\n\ninterface DatafileLoader {\n  load(sdkKey: string, options?: LoadDatafileOptions): Promise<FlagDatafile | null>;\n  clear(): void;\n}\n\nfunction createDatafileLoader(options?: {\n  fetchDatafile?: DatafileFetcher;\n  now?: () => number;\n}): DatafileLoader;\n\n/** The loader the module's own plugin uses. One per server process. */\nconst avsbDatafileLoader: DatafileLoader;\n```\n\n`AvsbServer` from `@avsbhq/node` satisfies `AvsbServerClient`, so nothing needs casting.\n\n### Turning it off\n\n```ts\navsb: {\n  bootstrap: false;\n}\n```\n\nThe datafile then stays out of the HTML and the browser fetches it itself. The trade is honest: smaller payload, and the first paint shows the default value you passed until the datafile lands.\n\n---\n\n## 8. Identity\n\nWithout a context the visitor gets a persisted anonymous id (localStorage, with a cookie fallback), so a returning visitor buckets into the same variation instead of being re-randomised on every load.\n\nFor a signed-in user, set the context for the request on the server and both sides evaluate the same person:\n\n```ts\n// plugins/avsb-identity.server.ts\nimport { defineNuxtPlugin, useCookie, useState } from 'nuxt/app';\nimport { AVSB_CONTEXT_STATE_KEY } from '@avsbhq/nuxt/server';\nimport type { EvalContext } from '@avsbhq/core';\n\nexport default defineNuxtPlugin(() => {\n  const userId = useCookie<string | null>('uid').value;\n  const context = useState<EvalContext | null>(AVSB_CONTEXT_STATE_KEY, () => null);\n\n  if (userId) context.value = { kind: 'user', key: userId, plan: 'pro' };\n});\n```\n\nAfter a login in the browser, switch identity through the composable:\n\n```ts\n// Auto-imported in a Nuxt app. This is the import line you write when\n// `autoImports` is off.\nimport { useAlias, useIdentify, useReset } from '@avsbhq/vue';\n\ndeclare const anonymousKey: string;\ndeclare const user: { id: string; email: string; plan: string };\n\nconst identify = useIdentify();\nconst alias = useAlias();\nconst reset = useReset();\n\n// Same person, two sessions: this is what lets results stitch them.\nalias({ kind: 'user', key: anonymousKey }, { kind: 'user', key: user.id });\nidentify({ kind: 'user', key: user.id, email: user.email, plan: user.plan });\n\n// On sign-out: a new anonymous identity, runtime overrides cleared.\nreset();\n```\n\nMulti-context works the same way it does in every A vs B SDK:\n\n```ts\nconst workspaceContext: EvalContext = {\n  kind: 'multi',\n  user: { kind: 'user', key: 'u_123', plan: 'pro' },\n  organization: { kind: 'organization', key: 'org_456', tier: 'enterprise' },\n};\n\n// On the server, in the plugin above: context.value = workspaceContext\nidentify(workspaceContext);\n```\n\n---\n\n## 9. Readiness and failure\n\n```vue\n<script setup lang=\"ts\">\nconst { status, error, degraded } = useAvsbStatus();\n</script>\n\n<template>\n  <SkeletonApp v-if=\"status === 'loading'\" />\n  <template v-else>\n    <StaleDataNotice v-if=\"degraded\" :message=\"error?.message\" />\n    <NuxtPage />\n  </template>\n</template>\n```\n\nWith the default bootstrap the status is already `'ready'` on the first render, so this is a safety net rather than a loading screen you will see.\n\n- `'loading'`: no datafile yet and nothing cached.\n- `'ready'`: flags answer. A **degraded** client is ready: a refresh failed while a cached datafile is being served, so values work and may be stale. `error` says why.\n- `'error'`: nothing could be loaded. Every flag returns the default you passed, and `error.message` names the HTTP status, the URL tried, and the fix.\n\nThe SDK's default logger writes to the console at `warn` level in development and is silent in production. Change it with `avsb: { client: { logLevel: 'debug' } }`.\n\n---\n\n## 10. Testing\n\nComponent tests do not go through Nuxt's plugin, so use the Vue package's test provider directly:\n\n```ts\nimport { mount } from '@vue/test-utils';\nimport { AvsbTestProvider } from '@avsbhq/vue/testing';\nimport CheckoutButton from './CheckoutButton.vue';\n\nconst wrapper = mount(AvsbTestProvider, {\n  props: { flags: { 'checkout-v2': true } },\n  slots: { default: CheckoutButton },\n});\n```\n\nThat is the real provider around a real client whose datafile is built from your flags map, with the network, the cache and logging switched off. Your components use the same composables and the same evaluator they use in production.\n\n---\n\n## 11. What this module does not do\n\n- It does not evaluate flags inside Nitro server routes. For server-side evaluation, use `@avsbhq/node` in your Nitro code and (optionally) hand its datafile to the page with `getAvsbBootstrap`.\n- It does not register components. The composables are the API.\n- It does not add its own devtools panel.\n\n---\n\n## 12. Set this up with your AI assistant\n\nPaste this into Claude Code, Cursor, or any coding assistant:\n\n```text\nSet up A vs B feature flags in this Nuxt project.\n\n1. Run: npx @avsbhq/cli init\n   Use my saved CLI login: do not ask me for a token and do not put one in any file.\n   It detects Nuxt, writes the SDK key into .env as NUXT_PUBLIC_AVSB_SDK_KEY, and\n   writes an example component.\n2. Install @avsbhq/nuxt with this project's package manager.\n3. Add \"@avsbhq/nuxt\" to the modules array in nuxt.config.ts. There is no provider\n   to mount: the module auto-imports the composables.\n4. Read the flag the example names with useBoolFlag(key, false). Every getter\n   returns a Flag object, so read .value or call .isEnabled(), and always pass a\n   fallback. Reading a flag records nothing: call useExposure(key) where the\n   variation is shown.\n5. Then run: npx @avsbhq/cli codegen\n\nDone looks like: the app starts, the flag reads without throwing, and avsb init\nprints the line confirming it saw the first check-in.\n```\n\n`avsb init` ends by waiting for your app's first check-in and printing what it\nsaw, so the terminal tells you it works rather than the dashboard.\n","readmeFilename":"README.md"}