# Vue

Source: https://docs.mirafive.io/sdks/vue

> Add MIRA FIVE analytics and feature flags to a Vue 3 app with a plugin and composables that render the same on server and client.

`@mirafive/sdk-vue` adds a Vue plugin and composables on top of the [browser SDK](https://docs.mirafive.io/sdks/browser): `useMira()` for the client, and `useFlag()` and `useFlagConfig()` as refs that follow flag loads. Use it for Vue 3 apps built with Vite, with or without Vue Router, and for your own Vue server rendering. Using Nuxt? Use [`@mirafive/sdk-nuxt`](https://docs.mirafive.io/sdks/nuxt) instead: it creates the client and wires the server side for you.

## Install

```bash
npm install @mirafive/sdk-vue @mirafive/sdk-browser
```

Requires Vue 3.5 or later and `@mirafive/sdk-browser` 1.x. The package is ESM only and adds 0.68 kB (min + gzip) on top of the browser SDK. It has no transport and no flag evaluator of its own: it hands you the client you created and reads flags from it.

## Set up

You need the **website key** of a website source (`mf_…`). See [Keys](https://docs.mirafive.io/keys) for where to find it. Put it in Vite's public env var:

```sh title=".env"
VITE_MIRAFIVE_KEY=mf_…
```

Create the client once, in the browser entry, and install the plugin before you mount:

```ts title="src/main.ts"
import { createMira } from '@mirafive/sdk-browser'
import { flags } from '@mirafive/sdk-browser/flags'
import { pageviews } from '@mirafive/sdk-browser/pageviews'
import { createMiraPlugin } from '@mirafive/sdk-vue'
import { createApp } from 'vue'

import App from './App.vue'

const mira = createMira({
  key: import.meta.env.VITE_MIRAFIVE_KEY,
  plugins: [pageviews(), flags()],
})

createApp(App).use(createMiraPlugin(mira)).mount('#app')
```

That is the whole install. `pageviews()` records the first page and every client-side navigation, Vue Router included, through the Navigation API or the History API. Do not add pageview calls to router hooks. Leave out `flags()` if the app reads no flags. `app.unmount()` destroys the client.

The client starts in [consentless mode](https://docs.mirafive.io/guides/consent#consentless-mode): it sets no cookies, stores nothing on the device and needs no consent banner.

## Verify

1. Open the app on its real domain. Events from `localhost` are dropped unless you pass `trackLocalhost: true` to `createMira`.
2. In the browser's network tab, look for `POST https://events.mirafive.io/v1/batch/mf_…` answering `202` with `"accepted": 1`. Batches leave 5 seconds after the first event, or when the tab is hidden.
3. Open the source's live view in MIRA FIVE and find the pageview.

Nothing arriving? See [Troubleshooting](#troubleshooting).

## Track events

Read the client with `useMira()` and call `track(name, properties)`:

```vue title="src/components/CheckoutButton.vue"
<script setup lang="ts">
import { useMira } from '@mirafive/sdk-vue'

const mira = useMira()
</script>

<template>
  <button @click="mira.track('checkout_started', { plan: 'pro' })">Checkout</button>
</template>
```

Call `useMira()` in `setup`, or in code that runs inside `app.runWithContext()`. Event names, property limits and revenue are covered in [Track events](https://docs.mirafive.io/guides/track-events).

Name your events in a type to have TypeScript check every call:

```ts
type Events = { signup: { plan: string }; logout: undefined }

const mira = useMira<Events>()
mira.track('signup', { plan: 'pro' }) // checked by TypeScript
```

## Identify users

Identifying users needs [full mode](https://docs.mirafive.io/guides/consent#full-mode) and the `identity()` plugin. Add both to `createMira`:

```ts title="src/main.ts"
import { createMira } from '@mirafive/sdk-browser'
import { flags } from '@mirafive/sdk-browser/flags'
import { identity } from '@mirafive/sdk-browser/identity'
import { pageviews } from '@mirafive/sdk-browser/pageviews'

const mira = createMira({
  key: import.meta.env.VITE_MIRAFIVE_KEY,
  mode: 'full',
  plugins: [pageviews(), identity(), flags()],
})
```

Then pass your consent manager's answer and the user id:

```ts
const mira = useMira()

// From your consent manager's callback:
mira.consent({ statistics: true, experiments: true, targeting: false })

// After login:
mira.identify(user.id, { plan: user.plan })

// After logout:
mira.reset()
```

Before a consent answer, full mode stores and sends nothing. The consent manager wiring (Cookiebot, OneTrust) is on the [browser SDK page](https://docs.mirafive.io/sdks/browser#consent). See [Identify users](https://docs.mirafive.io/guides/identify-users) and [Consent](https://docs.mirafive.io/guides/consent).

## Feature flags

With the `flags()` plugin on the client, read a flag's variant with `useFlag` and its remote config with `useFlagConfig`. Both return readonly refs: use `.value` in script, and the ref as is in templates.

```vue title="src/components/BuyButton.vue"
<script setup lang="ts">
import { useFlag, useFlagConfig } from '@mirafive/sdk-vue'

const newCheckout = useFlag('new-checkout', false)
const limits = useFlagConfig('checkout-limits', { maxItems: 10 })
</script>

<template>
  <button>{{ newCheckout === true ? 'Buy now' : `Checkout (max ${limits.maxItems})` }}</button>
</template>
```

`useFlag` holds exactly what `mira.flag(key, fallback)` answers: `true`/`false` for an on/off flag, the variant key for any other flag, whatever the fallback's type. It holds the fallback until flags load. The refs update when flags load or change, count an [exposure](https://docs.mirafive.io/guides/experiments) where `mira.flag()` would, and stop listening when their component or effect scope is disposed. Outside components, in Pinia stores or composables run in `app.runWithContext()`, they follow the client from the start. See [Feature flags](https://docs.mirafive.io/guides/feature-flags).

## Server rendering

`createMira()` needs a browser. In your own Vue server rendering, install the plugin with `undefined` as the client and the flag answers the server used. Get them from [`@mirafive/sdk-server/flags`](https://docs.mirafive.io/sdks/node#feature-flags):

```ts title="src/entry-server.ts"
import { bootstrapHeaders, MiraFlags } from '@mirafive/sdk-server/flags'
import { createMiraPlugin } from '@mirafive/sdk-vue'
import { createSSRApp } from 'vue'
import { renderToString } from 'vue/server-renderer'

import App from './App.vue'

const flags = new MiraFlags({ key: process.env.MIRAFIVE_SECRET_KEY })

export async function render(request: Request, userId: string | undefined) {
  const optedOut = request.headers.get('sec-gpc') === '1' || request.headers.get('dnt') === '1'
  const user = await flags.for({ userId, optedOut })
  const bootstrap = user.bootstrap()
  const app = createSSRApp(App).use(createMiraPlugin(undefined, { bootstrap }))
  const html = await renderToString(app)

  // Put `bootstrap` into <head> and send `bootstrapHeaders` (Cache-Control: private, no-store).
  return { html, head: bootstrap, headers: bootstrapHeaders }
}
```

In the browser, hydrate with the live client. The plugin reads the page's `<script id="mirafive-flags">` block itself, and so does `flags()`:

```ts title="src/entry-client.ts"
import { createMira } from '@mirafive/sdk-browser'
import { flags } from '@mirafive/sdk-browser/flags'
import { pageviews } from '@mirafive/sdk-browser/pageviews'
import { createMiraPlugin } from '@mirafive/sdk-vue'
import { createSSRApp } from 'vue'

import App from './App.vue'

const mira = createMira({
  key: import.meta.env.VITE_MIRAFIVE_KEY,
  plugins: [pageviews(), flags()],
})

createSSRApp(App).use(createMiraPlugin(mira)).mount('#app')
```

During the server render and the hydration after it, the flag refs answer the bootstrap, so both produce the same markup. After the component mounts they switch to the live client. A component first mounted later reads the live client at once. A bootstrap older than 7 days is ignored, as the browser SDK ignores it. During a server render, `useMira()` returns a stand-in: its calls do nothing, `flag` and `config` return the fallback, and `flush()` resolves.

## API reference

```ts
import { createMiraPlugin, useFlag, useFlagConfig, useMira } from '@mirafive/sdk-vue'
import type { FlagBootstrap, Json, Mira, MiraPluginOptions } from '@mirafive/sdk-vue'
```

| Export | Description |
| --- | --- |
| `createMiraPlugin(client, options?)` | The Vue plugin: `app.use(createMiraPlugin(mira))`. `client` is a `Mira` from `createMira()`, or `undefined` on a server. `app.unmount()` destroys the client. |
| `useMira<Events>(): Mira<Events>` | The client. During a server render, a stand-in whose calls do nothing and whose `flag`/`config` return the fallback; it is not a thenable, so `await useMira()` resolves. Throws when the plugin is not installed. |
| `useFlag(key, fallback: string \| boolean): Readonly<Ref<string \| boolean>>` | What `mira.flag(key, fallback)` answers: the variant, or `true`/`false` for an on/off flag. |
| `useFlagConfig<T = Json>(key, fallback: T): Readonly<Ref<T>>` | What `mira.config(key, fallback)` answers: the remote-config value of the flag's variant. |

`createMiraPlugin` options (`MiraPluginOptions`):

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `bootstrap` | `FlagBootstrap \| string` | the page's `#mirafive-flags` block, in a browser | The server's flag answers: the `FlagBootstrap` object or the HTML string of `user.bootstrap()`. The refs answer only it during a server render and hydration. |

Everything the client can do (`track`, `identify`, `consent`, `flush`, …) is listed in the [browser SDK reference](https://docs.mirafive.io/sdks/browser#api-reference).

## Troubleshooting

| Symptom | Cause and fix |
| --- | --- |
| Nothing arrives | Events from `localhost` are off by default (`trackLocalhost: true`). Do Not Track or Global Privacy Control is on in the browser. In mode `'full'`, `consent()` has not run. |
| `403 origin_not_allowed` | Add the site's domain to the source in MIRA FIVE. |
| `403 secret_key_in_path` | You passed a secret key to the browser. Use the website key of a website source. |
| A flag always returns its fallback | The client has no `flags()` plugin, the flag is not enabled for this source, a segment rule lacks `targeting` consent, or flags have not loaded yet (the ref updates when they do). |
| Hydration mismatch on a flag | The server rendered with a different bootstrap than the page carries. Pass `user.bootstrap()` to `createMiraPlugin` on the server and put the identical block into the head. |
| `[mirafive] install the plugin first` | `app.use(createMiraPlugin(mira))` is missing, or `useMira()` ran outside `setup` and outside `app.runWithContext()`. |

## Set up with an AI agent

Paste this into your coding agent:

```text
Add MIRA FIVE analytics and feature flags to this Vue 3 app with @mirafive/sdk-vue.
Docs: https://docs.mirafive.io/sdks/vue.md

1. Install @mirafive/sdk-vue and @mirafive/sdk-browser with the project's package manager.
   If this is a Nuxt app, use @mirafive/sdk-nuxt instead (https://docs.mirafive.io/sdks/nuxt.md).
2. Put the source's website key (mf_…) in VITE_MIRAFIVE_KEY. Never put MIRAFIVE_SECRET_KEY in browser code.
3. In the client entry (main.ts), before mount:
     import { createMira } from '@mirafive/sdk-browser'
     import { pageviews } from '@mirafive/sdk-browser/pageviews'
     import { flags } from '@mirafive/sdk-browser/flags'
     import { createMiraPlugin } from '@mirafive/sdk-vue'
     const mira = createMira({ key: import.meta.env.VITE_MIRAFIVE_KEY, plugins: [pageviews(), flags()] })
     app.use(createMiraPlugin(mira))
   Leave out flags() if the app reads no flags. Do not add pageview calls to router hooks.
4. In components: const mira = useMira(), then mira.track('name', { … }) in handlers;
   const on = useFlag('key', false) for flags (a readonly ref).
5. Keep the default consentless mode, which needs no consent banner. Only if the app already has a
   consent manager and needs user ids: add identity() from '@mirafive/sdk-browser/identity',
   set mode: 'full', and call mira.consent({ statistics, experiments, targeting }) from its callback.
6. With server rendering: on the server use createMiraPlugin(undefined, { bootstrap: user.bootstrap() })
   with MiraFlags from '@mirafive/sdk-server/flags', put the same block into <head>, and send
   Cache-Control: private, no-store with that response.
7. Verify: load a page on a non-localhost domain (or pass trackLocalhost: true) and check the network
   tab for POST https://events.mirafive.io/v1/batch/<key> answering 202. Report what you changed.
Do not add other analytics libraries, cookies or consent banners.
```
