[{"data":1,"prerenderedAt":207},["ShallowReactive",2],{"doc:\u002Fdocs\u002Fdevice-fingerprinting-vue-nuxt":3},{"page":4,"toc":183,"updated":206},{"path":5,"title":6,"seoTitle":7,"description":8,"blocks":9},"\u002Fdocs\u002Fdevice-fingerprinting-vue-nuxt","Add device fingerprinting to a Vue or Nuxt app","Vue and Nuxt Device Fingerprinting: Visitor ID Tutorial","Add device fingerprinting to a Vue 3 or Nuxt app: install the plugin or module, identify with a composable, and verify the result in a server route.",[10,13,18,24,27,37,40,78,81,111,114,116,121,123,127,130,132,135,139,141,144,146,149,151,154,156,159,161],{"type":11,"text":12},"p","This tutorial adds **device fingerprinting** to a Vue 3 app or a Nuxt app. A plugin or module gives every component one shared identification as readonly refs, a composable asks for it when the visitor acts, and your server reads the stored result before it decides.",{"type":14,"level":15,"text":16,"id":17},"heading",2,"Before you start","before-you-start",{"type":19,"items":20},"list",[21,22,23],"Vue 3.3 or newer, or Nuxt 3 or 4. In Nuxt, use the module: it registers the Vue plugin and auto-imports the composables.","A public key with your app's origin in its allowed origins, and a secret key for your server.","A decision about consent: the SDK collects nothing until `consent` is `granted`.",{"type":14,"level":15,"text":25,"id":26},"Get your keys","get-your-keys",{"type":28,"steps":29},"steps",[30],{"title":31,"blocks":32},"Create your keys",[33,35],{"type":11,"text":34},"In the Fingerly dashboard, open **Integration > SDK keys** and create a **public** key in the **development** environment. Add the origin your app runs on as an allowed origin. A public key with no allowed origins refuses every browser request. Then create a **secret** key in the same environment for your server.",{"type":11,"text":36},"Development keys run the same detection as production and cost nothing. See [API keys and environments](\u002Fdocs\u002Fapi-keys).",{"type":14,"level":15,"text":38,"id":39},"Add it to a Vue 3 app","add-it-to-a-vue-3-app",{"type":28,"steps":41},[42,66],{"title":43,"blocks":44},"Install the plugin",[45,58,64],{"type":46,"samples":47},"code",[48,52,55],{"label":49,"lang":50,"code":51},"npm","bash","npm install @fingerly\u002Fvue",{"label":53,"lang":50,"code":54},"pnpm","pnpm add @fingerly\u002Fvue",{"label":56,"lang":50,"code":57},"yarn","yarn add @fingerly\u002Fvue",{"type":46,"samples":59},[60],{"label":61,"lang":62,"code":63},"main.ts","ts","import { createApp } from 'vue'\nimport { createFingerly } from '@fingerly\u002Fvue'\nimport App from '.\u002FApp.vue'\n\ncreateApp(App)\n  \u002F\u002F consent: 'granted' only once you have consent (or another lawful basis)\n  .use(createFingerly({ apiKey: 'fly_pk_development_…', consent: 'granted' }))\n  .mount('#app')",{"type":11,"text":65},"Installing collects nothing. `consent` defaults to `pending`, and you can change it at runtime with `useFingerly().setConsent()`.",{"title":67,"blocks":68},"Identify when the visitor acts",[69,76],{"type":46,"samples":70},[71],{"label":72,"lang":73,"code":74,"check":75},"SignUpForm.vue","vue","\u003Cscript setup lang=\"ts\">\nimport { ref } from 'vue'\nimport { useIdentify } from '@fingerly\u002Fvue'\n\nconst email = ref('')\nconst { identify, isLoading, error } = useIdentify({ tag: 'signup' })\n\nasync function onSubmit() {\n  let requestId: string | undefined\n  try {\n    ;({ requestId } = await identify())\n  } catch {\n    \u002F\u002F Carry on: your server treats a missing request ID as missing evidence.\n  }\n  await fetch('\u002Fapi\u002Fsignup', {\n    method: 'POST',\n    headers: { 'Content-Type': 'application\u002Fjson' },\n    body: JSON.stringify({ email: email.value, requestId }),\n  })\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cform @submit.prevent=\"onSubmit\">\n    \u003Cinput v-model=\"email\" type=\"email\" required \u002F>\n    \u003Cbutton :disabled=\"isLoading\">{{ isLoading ? 'Checking…' : 'Create account' }}\u003C\u002Fbutton>\n    \u003Cp v-if=\"error\">{{ error.message }}\u003C\u002Fp>\n  \u003C\u002Fform>\n\u003C\u002Ftemplate>",false,{"type":11,"text":77},"Every returned field is a readonly ref, and `isReady` and `isSuspicious` are computed refs. A call while an identification is in flight gets that identification, and a finished result is reused until you call `refresh()`.",{"type":14,"level":15,"text":79,"id":80},"Add it to a Nuxt app","add-it-to-a-nuxt-app",{"type":28,"steps":82},[83,101],{"title":84,"blocks":85},"Install the module",[86,94,99],{"type":46,"samples":87},[88,90,92],{"label":49,"lang":50,"code":89},"npm install @fingerly\u002Fnuxt",{"label":53,"lang":50,"code":91},"pnpm add @fingerly\u002Fnuxt",{"label":56,"lang":50,"code":93},"yarn add @fingerly\u002Fnuxt",{"type":46,"samples":95},[96],{"label":97,"lang":62,"code":98},"nuxt.config.ts","export default defineNuxtConfig({\n  modules: ['@fingerly\u002Fnuxt'],\n  fingerly: {\n    apiKey: 'fly_pk_development_…',\n    \u002F\u002F 'granted' only once you have consent (or another lawful basis)\n    consent: 'granted',\n  },\n})",{"type":11,"text":100},"Or leave the key out of the config and set `NUXT_PUBLIC_FINGERLY_API_KEY` in the environment. The environment wins, and Nuxt reads it when the server starts, so one build can move through staging and production with a different key in each.",{"title":102,"blocks":103},"Identify in any component",[104,106],{"type":11,"text":105},"`useIdentify` and `useFingerly` are auto-imported. The plugin runs on the server and the client, so the composables work during server rendering, where they return the unidentified state. Identification itself only happens in the browser.",{"type":46,"samples":107},[108],{"label":109,"lang":73,"code":110,"check":75},"components\u002FSignUpForm.vue","\u003Cscript setup lang=\"ts\">\nconst { identify, isLoading } = useIdentify({ tag: 'signup' })\n\nasync function onSubmit(form: { email: string }) {\n  const { requestId } = await identify()\n  await $fetch('\u002Fapi\u002Fsignup', { method: 'POST', body: { ...form, requestId } })\n}\n\u003C\u002Fscript>",{"type":14,"level":15,"text":112,"id":113},"Verify in a server route","verify-in-a-server-route",{"type":11,"text":115},"Whichever front end you use, decide on the server. In Nuxt, a Nitro route reads the stored event with your secret key and checks it belongs to the action. In a plain Vue app, put the same code in whichever backend you run.",{"type":46,"samples":117},[118],{"label":119,"lang":62,"code":120},"server\u002Fapi\u002Fsignup.post.ts","import { load } from '@fingerly\u002Fnode'\n\nconst fingerly = load({ secretKey: process.env.FINGERLY_SECRET_KEY! })\n\nexport default defineEventHandler(async (event) => {\n  const { email, requestId } = await readBody(event)\n  const result = await fingerly.events.get(requestId)\n  if (result.tag !== 'signup') throw createError({ statusCode: 400 })\n  \u002F\u002F decide with result.suspect_level\n})",{"type":11,"text":122},"Check the event is recent and has not been used before as well, and answer a pending event with a retry rather than a refusal. [Server-side verification](\u002Fdocs\u002Fserver-side-verification) lists the four checks.",{"type":124,"tone":125,"text":126},"callout","tip","Run the app, submit the form and open **Identification > Events** in the dashboard to see the visitor, score and signals for your own session.",{"type":14,"level":15,"text":128,"id":129},"Get past content blockers","get-past-content-blockers",{"type":11,"text":131},"Mount a first-party proxy at the path the dashboard issues and give the plugin or module that path as `endpoints`. The [Nuxt SDK page](\u002Fdocs\u002Fsdks\u002Fnuxt#serve-it-from-your-own-site) has the Nitro route, and [proxy integrations](\u002Fdocs\u002Fproxy-integrations) explains the model.",{"type":14,"level":15,"text":133,"id":134},"Common questions","common-questions",{"type":14,"level":136,"text":137,"id":138},3,"How do I get a visitor ID in Vue?","how-do-i-get-a-visitor-id-in-vue",{"type":11,"text":140},"Install the plugin, call `identify()` from `useIdentify()` when the visitor acts, and read `visitorId` from the result. Send the `requestId` to your server and read the stored event there.",{"type":14,"level":136,"text":142,"id":143},"Does it work with the Options API?","does-it-work-with-the-options-api",{"type":11,"text":145},"Yes. The plugin adds `this.$fingerly`, so `await this.$fingerly.identify()` works in `mounted()` or a method. The [Vue SDK page](\u002Fdocs\u002Fsdks\u002Fvue#options-api) has an example.",{"type":14,"level":136,"text":147,"id":148},"Does it work with server rendering?","does-it-work-with-server-rendering",{"type":11,"text":150},"Yes. `immediate` runs in `onMounted`, which never runs on the server, and calling `identify()` during server rendering rejects with `FingerlyServerError`. In a custom SSR setup, create the plugin per app instance rather than at module scope.",{"type":14,"level":136,"text":152,"id":153},"How do I set the key per environment in Nuxt?","how-do-i-set-the-key-per-environment-in-nuxt",{"type":11,"text":155},"Set `NUXT_PUBLIC_FINGERLY_API_KEY`. The options are published to `runtimeConfig.public.fingerly`, so the key can differ between staging and production without a rebuild.",{"type":14,"level":136,"text":157,"id":158},"What if the key is missing?","what-if-the-key-is-missing",{"type":11,"text":160},"The Nuxt build still succeeds because the key can arrive from the environment later. The plugin warns once per process, the app renders, and `identify()` fails with a missing-key error until a key is set.",{"type":162,"columns":15,"cards":163},"cards",[164,168,173,178],{"title":165,"text":166,"href":167,"logo":73},"Vue SDK","Plugin, composables and errors.","\u002Fdocs\u002Fsdks\u002Fvue",{"title":169,"text":170,"href":171,"logo":172},"Nuxt SDK","Module options and the proxy route.","\u002Fdocs\u002Fsdks\u002Fnuxt","nuxt",{"title":174,"text":175,"href":176,"icon":177},"Server-side verification","The four checks before you trust a result.","\u002Fdocs\u002Fserver-side-verification","shield",{"title":179,"text":180,"href":181,"icon":182},"Bot, VPN and Tor detection","Read the network and automation fields.","\u002Fdocs\u002Fbot-and-vpn-detection","bot",[184,185,186,188,189,191,193,194,196,198,199,200,201,202,203,204,205],{"id":17,"text":16,"level":15},{"id":26,"text":25,"level":15},{"id":187,"text":31,"level":136},"step-create-your-keys",{"id":39,"text":38,"level":15},{"id":190,"text":43,"level":136},"step-install-the-plugin",{"id":192,"text":67,"level":136},"step-identify-when-the-visitor-acts",{"id":80,"text":79,"level":15},{"id":195,"text":84,"level":136},"step-install-the-module",{"id":197,"text":102,"level":136},"step-identify-in-any-component",{"id":113,"text":112,"level":15},{"id":129,"text":128,"level":15},{"id":134,"text":133,"level":15},{"id":138,"text":137,"level":136},{"id":143,"text":142,"level":136},{"id":148,"text":147,"level":136},{"id":153,"text":152,"level":136},{"id":158,"text":157,"level":136},"2026-11-12T11:59:51.000Z",1794493376692]