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: 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 instead: it creates the client and wires the server side for you.
Install
npm install @mirafive/sdk-vue @mirafive/sdk-browserRequires 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 for where to find it. Put it in Vite's public env var:
VITE_MIRAFIVE_KEY=mf_…Create the client once, in the browser entry, and install the plugin before you mount:
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: it sets no cookies, stores nothing on the device and needs no consent banner.
Verify
- Open the app on its real domain. Events from
localhostare dropped unless you passtrackLocalhost: truetocreateMira. - In the browser's network tab, look for
POST https://events.mirafive.io/v1/batch/mf_…answering202with"accepted": 1. Batches leave 5 seconds after the first event, or when the tab is hidden. - Open the source's live view in MIRA FIVE and find the pageview.
Nothing arriving? See Troubleshooting.
Track events
Read the client with useMira() and call track(name, properties):
<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.
Name your events in a type to have TypeScript check every call:
type Events = { signup: { plan: string }; logout: undefined }
const mira = useMira<Events>()
mira.track('signup', { plan: 'pro' }) // checked by TypeScriptIdentify users
Identifying users needs full mode and the identity() plugin. Add both to createMira:
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:
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. See Identify users and 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.
<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 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.
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:
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():
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
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.
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:
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.