Framework guides
Nuxt
Evaluate on the Nuxt server, and send components only the results. The SDK and the datafile never reach the browser, so a visitor sees the results your routes return, not every feature and the audiences it targets.
Create a Nuxt application#
Create a Nuxt application, if you do not have one:
npm create nuxt@latest my-appInstall the SDK#
npm install @featurevisor/sdkThe JavaScript SDK page is the reference for every method.
Configure the datafile URL#
This example uses Nuxt 4 with a Node.js server deployment. Keep the datafile URL in private runtime configuration, not under public. Set NUXT_FEATUREVISOR_DATAFILE_URL in the deployed server's environment:
export default defineNuxtConfig({ runtimeConfig: { featurevisorDatafileUrl: '', },})Share one instance on the server#
Keep the SDK in the server directory. Save the shared ES module refresh helper unchanged as server/utils/datafile-refresh.mjs, without the Node.js startup timer.
Create server/utils/featurevisor.mjs:
import { createFeaturevisor } from '@featurevisor/sdk'import { refreshDatafile } from './datafile-refresh.mjs'const instance = createFeaturevisor({})let ready = falselet nextRefreshAt = 0export async function getInstance(datafileUrl) { if (!datafileUrl) throw new Error('Set NUXT_FEATUREVISOR_DATAFILE_URL') if (Date.now() >= nextRefreshAt) { try { await refreshDatafile(instance, datafileUrl) ready = true nextRefreshAt = Date.now() + 5 * 60 * 1000 } catch (error) { if (!ready) throw error nextRefreshAt = Date.now() + 30 * 1000 console.error('Configuration refresh failed:', error) } } return instance}Each worker waits for its first valid datafile, then refreshes on requests after five minutes. Failures retain the previous datafile and retry after thirty seconds. Keep one fixed URL per instance. Requests trigger refreshes, so no timer is needed.
Evaluate in an API route#
Evaluate in a server API route. This route assumes your authentication middleware has already verified the session and populated event.context.user.id. It does not trust a user ID supplied as a query parameter. If your middleware uses another field, adapt that lookup.
import { getInstance } from '../utils/featurevisor.mjs'export default defineEventHandler(async (event) => { setHeader(event, 'Cache-Control', 'private, no-store') const user = event.context.user if (!user?.id) return { checkout: false } const config = useRuntimeConfig(event) const f = await getInstance(config.featurevisorDatafileUrl) const context = { userId: String(user.id), country: user.country, } return { checkout: f.isEnabled('checkout', context), }})Pass context to each evaluation. Never call setContext() on the shared instance for an individual request. Feature flags do not replace authorization checks.
Read the results in a component#
Fetch the results from the route:
<script setup lang="ts">const { data: features, error, refresh } = await useFetch('/api/features')</script><template> <div> <p v-if="error">Configuration is temporarily unavailable.</p> <p v-else-if="features?.checkout">Checkout is enabled.</p> <p v-else>Checkout is disabled.</p> <button @click="() => refresh()">Refresh configuration</button> </div></template>With a relative URL, Nuxt's request fetch handling forwards request context and cookies during server rendering. useFetch transfers the result in the Nuxt payload for hydration, without sending the SDK or datafile to the browser. Call refresh() after login or logout before showing results for the new identity. Refreshing the component respects the server's five minute datafile expiry. It does not force a CDN fetch.
Inside a server rendered component or server plugin, useRequestEvent() exposes the incoming event. It is undefined in the browser. In this API route, use the supplied event directly instead of a Nuxt composable.
Do not prerender or apply shared response caching to personalized pages or this API route. Configure any proxy or CDN to honour that policy for both HTML and payloads.
Visitors who are not signed in#
The route above returns a disabled checkout to anyone not signed in, because it has no stable ID to bucket them by. To include them in rollouts, set a cookie with a random ID on their first visit, and pass it as deviceId for features that bucket by it. Use the same ID on the server and in the browser, and never fall back to one ID shared by every visitor.
Related#
- Vue.js, for an application rendered in the browser
- Deployment
- Example application

