Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions examples/example-nuxt/pages/feature-flags.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<div style="padding: 20px; font-family: sans-serif">
<h1>Feature Flags Testing Page</h1>
<p>Test the PostHog feature flag composables:</p>

<div style="margin-top: 20px">
<h2>Feature Flag Examples</h2>

<div style="margin-top: 15px; padding: 15px; background-color: #f5f5f5; border-radius: 8px">
<h3>1. Check if flag is enabled</h3>
<p><strong>Flag:</strong> beta-feature</p>
<p><strong>Enabled:</strong> {{ featureFlagEnabled ?? 'Loading...' }}</p>
</div>

<div style="margin-top: 15px; padding: 15px; background-color: #f5f5f5; border-radius: 8px">
<h3>2. Get flag variant/value</h3>
<p><strong>Flag:</strong> test-variant</p>
<p><strong>Variant:</strong> {{ featureFlagVariant ?? 'Loading...' }}</p>
</div>

<div style="margin-top: 15px; padding: 15px; background-color: #f5f5f5; border-radius: 8px">
<h3>3. Get flag payload</h3>
<p><strong>Flag:</strong> config-flag</p>
<p><strong>Payload:</strong> {{ payloadDisplay }}</p>
</div>

<div style="margin-top: 15px; padding: 15px; background-color: #f5f5f5; border-radius: 8px">
<h3>4. Use PostHog directly</h3>
<button @click="captureEvent" style="padding: 10px; cursor: pointer; margin-right: 10px">
Capture Event
</button>
<button @click="getAllFlags" style="padding: 10px; cursor: pointer">Get All Flags</button>
<p v-if="allFlags" style="margin-top: 10px">
<strong>All Flags:</strong>
<pre>{{ JSON.stringify(allFlags, null, 2) }}</pre>
</p>
</div>
</div>

<div style="margin-top: 20px">
<a href="/" style="color: blue; text-decoration: underline">← Back to Error Testing</a>
</div>
</div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'

// Using the new composables
const featureFlagEnabled = useFeatureFlagEnabled('beta-feature')
const featureFlagVariant = useFeatureFlagVariantKey('test-variant')
const featureFlagPayload = useFeatureFlagPayload('config-flag')
const posthog = usePostHog()

const allFlags = ref<Record<string, boolean | string> | undefined>()

const payloadDisplay = computed(() => {
if (featureFlagPayload.value === undefined) {
return 'Loading...'
}
if (featureFlagPayload.value === null) {
return 'No payload'
}
return JSON.stringify(featureFlagPayload.value, null, 2)
})

const captureEvent = () => {
posthog?.capture('feature_flags_test_event', {
page: 'feature-flags',
})
alert('Event captured!')
}

const getAllFlags = () => {
allFlags.value = posthog?.featureFlags.getFlagVariants()
}
</script>
4 changes: 4 additions & 0 deletions examples/example-nuxt/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@

<button @click="redirectRenderingError" style="padding: 10px; cursor: pointer">8. Rendering Error</button>
</div>

<div style="margin-top: 20px">
<a href="/feature-flags" style="color: blue; text-decoration: underline">→ Go to Feature Flags Testing</a>
</div>
</div>
</template>

Expand Down
69 changes: 69 additions & 0 deletions packages/nuxt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,75 @@ export default defineNuxtConfig({
const { $posthog } = useNuxtApp()
```

## Composables

The module provides auto-imported composables for working with feature flags:

### `usePostHog()`

Returns the PostHog client instance.

```vue
<script setup>
const posthog = usePostHog()

posthog.capture('event_name')
</script>
```

### `useFeatureFlagEnabled(flag: string)`

Returns a reactive ref that checks if a feature flag is enabled (returns `true`/`false`/`undefined`).

```vue
<script setup>
const isEnabled = useFeatureFlagEnabled('my-flag')
</script>

<template>
<div v-if="isEnabled">
Feature is enabled!
</div>
</template>
```

### `useFeatureFlagVariantKey(flag: string)`

Returns a reactive ref containing the feature flag value/variant (returns the variant string, `true`/`false`, or `undefined`).

```vue
<script setup>
const variant = useFeatureFlagVariantKey('my-flag')
</script>

<template>
<div v-if="variant === 'variant-a'">
Show variant A
</div>
<div v-else-if="variant === 'variant-b'">
Show variant B
</div>
</template>
```

### `useFeatureFlagPayload(flag: string)`

Returns a reactive ref containing the feature flag payload (returns any JSON value or `undefined`).

```vue
<script setup>
const payload = useFeatureFlagPayload('config-flag')
</script>

<template>
<div v-if="payload">
Config value: {{ payload.value }}
</div>
</template>
```

All these composables automatically update when feature flags are loaded or changed.

4. On the server side, the PostHog client instance initialized by the plugin is intended exclusively for error tracking. If you require additional PostHog client functionality for other purposes, please instantiate a separate client within your application as needed.

## FAQ
Expand Down
3 changes: 2 additions & 1 deletion packages/nuxt/src/module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineNuxtModule, addPlugin, createResolver, addServerPlugin } from '@nuxt/kit'
import { defineNuxtModule, addPlugin, createResolver, addServerPlugin, addImportsDir } from '@nuxt/kit'
import type { PostHogConfig } from 'posthog-js'
import type { PostHogOptions } from 'posthog-node'
import { resolveBinaryPath, spawnLocal } from '@posthog/core/process'
Expand Down Expand Up @@ -58,6 +58,7 @@ export default defineNuxtModule<ModuleOptions>({
const resolver = createResolver(import.meta.url)
addPlugin(resolver.resolve('./runtime/vue-plugin'))
addServerPlugin(resolver.resolve('./runtime/nitro-plugin'))
addImportsDir(resolver.resolve(`./runtime/composables`))

Object.assign(nuxt.options.runtimeConfig.public, {
posthog: {
Expand Down
35 changes: 35 additions & 0 deletions packages/nuxt/src/runtime/composables/useFeatureFlagEnabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ref, onMounted } from 'vue'
import { usePostHog } from './usePostHog'

/**
* Check if a feature flag is enabled
*
* @example
* ```ts
* const isEnabled = useFeatureFlagEnabled('my-flag')
* ```
*
* @param flag - The feature flag key
* @returns A reactive ref containing the feature flag enabled state (boolean | undefined)
*/
export function useFeatureFlagEnabled(flag: string) {
const posthog = usePostHog()
const featureEnabled = ref<boolean | undefined>(posthog?.isFeatureEnabled?.(flag))

onMounted(() => {
if (!posthog) return

// Update when feature flags are loaded
const unsubscribe = posthog.onFeatureFlags?.(() => {
featureEnabled.value = posthog.isFeatureEnabled(flag)
})

return () => {
if (unsubscribe) {
unsubscribe()
}
}
})

return featureEnabled
}
37 changes: 37 additions & 0 deletions packages/nuxt/src/runtime/composables/useFeatureFlagPayload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ref, onMounted } from 'vue'
import { usePostHog } from './usePostHog'
import type { JsonType } from 'posthog-js'

/**
* Get the payload of a feature flag
*
* @example
* ```ts
* const payload = useFeatureFlagPayload('my-flag')
* ```
*
* @param flag - The feature flag key
* @returns A reactive ref containing the feature flag payload
*/
export function useFeatureFlagPayload(flag: string) {
const posthog = usePostHog()
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const featureFlagPayload = ref<JsonType>(posthog?.getFeatureFlagPayload?.(flag))

onMounted(() => {
if (!posthog) return

// Update when feature flags are loaded
const unsubscribe = posthog.onFeatureFlags?.(() => {
featureFlagPayload.value = posthog.getFeatureFlagPayload(flag)
})

return () => {
if (unsubscribe) {
unsubscribe()
}
}
})

return featureFlagPayload
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ref, onMounted } from 'vue'
import { usePostHog } from './usePostHog'

/**
* Get the value/variant of a feature flag
*
* @example
* ```ts
* const variant = useFeatureFlagVariantKey('my-flag')
* ```
*
* @param flag - The feature flag key
* @returns A reactive ref containing the feature flag value (string | boolean | undefined)
*/
export function useFeatureFlagVariantKey(flag: string) {
const posthog = usePostHog()
const featureFlagVariantKey = ref<string | boolean | undefined>(posthog?.getFeatureFlag?.(flag))

onMounted(() => {
if (!posthog) return

// Update when feature flags are loaded
const unsubscribe = posthog.onFeatureFlags?.(() => {
featureFlagVariantKey.value = posthog.getFeatureFlag(flag)
})

return () => {
if (unsubscribe) {
unsubscribe()
}
}
})

return featureFlagVariantKey
}
18 changes: 18 additions & 0 deletions packages/nuxt/src/runtime/composables/usePostHog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useNuxtApp } from '#app'
import type posthog from 'posthog-js'

/**
* Get the PostHog client instance
*
* @example
* ```ts
* const posthog = usePostHog()
* posthog.capture('event')
* ```
*
* @returns The PostHog client instance
*/
export function usePostHog(): typeof posthog | undefined {
const { $posthog } = useNuxtApp()
return ($posthog as () => typeof posthog | undefined)?.()
}