From 31a3f26ce2820ca478c3befe7f400d27f6109563 Mon Sep 17 00:00:00 2001 From: Geferon Date: Fri, 14 Nov 2025 01:08:12 +0100 Subject: [PATCH 1/4] feature(nuxt): Added feature flag composables --- examples/example-nuxt/pages/feature-flags.vue | 77 +++++++++++++++++++ examples/example-nuxt/pages/index.vue | 4 + packages/nuxt/README.md | 69 +++++++++++++++++ packages/nuxt/src/module.ts | 3 +- .../composables/useFeatureFlagEnabled.ts | 35 +++++++++ .../composables/useFeatureFlagPayload.ts | 37 +++++++++ .../composables/useFeatureFlagVariantKey.ts | 35 +++++++++ .../src/runtime/composables/usePostHog.ts | 18 +++++ 8 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 examples/example-nuxt/pages/feature-flags.vue create mode 100644 packages/nuxt/src/runtime/composables/useFeatureFlagEnabled.ts create mode 100644 packages/nuxt/src/runtime/composables/useFeatureFlagPayload.ts create mode 100644 packages/nuxt/src/runtime/composables/useFeatureFlagVariantKey.ts create mode 100644 packages/nuxt/src/runtime/composables/usePostHog.ts diff --git a/examples/example-nuxt/pages/feature-flags.vue b/examples/example-nuxt/pages/feature-flags.vue new file mode 100644 index 0000000000..0119734e13 --- /dev/null +++ b/examples/example-nuxt/pages/feature-flags.vue @@ -0,0 +1,77 @@ + + + diff --git a/examples/example-nuxt/pages/index.vue b/examples/example-nuxt/pages/index.vue index cce2307890..bc189bc21a 100644 --- a/examples/example-nuxt/pages/index.vue +++ b/examples/example-nuxt/pages/index.vue @@ -28,6 +28,10 @@ + +
+ → Go to Feature Flags Testing +
diff --git a/packages/nuxt/README.md b/packages/nuxt/README.md index c545d23231..10364ae3a8 100644 --- a/packages/nuxt/README.md +++ b/packages/nuxt/README.md @@ -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 + +``` + +### `useFeatureFlagEnabled(flag: string)` + +Returns a reactive ref that checks if a feature flag is enabled (returns `true`/`false`/`undefined`). + +```vue + + + +``` + +### `useFeatureFlagVariantKey(flag: string)` + +Returns a reactive ref containing the feature flag value/variant (returns the variant string, `true`/`false`, or `undefined`). + +```vue + + + +``` + +### `useFeatureFlagPayload(flag: string)` + +Returns a reactive ref containing the feature flag payload (returns any JSON value or `undefined`). + +```vue + + + +``` + +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 diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index 8676cba95f..5cb32d491e 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -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' @@ -58,6 +58,7 @@ export default defineNuxtModule({ 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: { diff --git a/packages/nuxt/src/runtime/composables/useFeatureFlagEnabled.ts b/packages/nuxt/src/runtime/composables/useFeatureFlagEnabled.ts new file mode 100644 index 0000000000..5d674e6f77 --- /dev/null +++ b/packages/nuxt/src/runtime/composables/useFeatureFlagEnabled.ts @@ -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(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 +} diff --git a/packages/nuxt/src/runtime/composables/useFeatureFlagPayload.ts b/packages/nuxt/src/runtime/composables/useFeatureFlagPayload.ts new file mode 100644 index 0000000000..224d3d85ec --- /dev/null +++ b/packages/nuxt/src/runtime/composables/useFeatureFlagPayload.ts @@ -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(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 +} diff --git a/packages/nuxt/src/runtime/composables/useFeatureFlagVariantKey.ts b/packages/nuxt/src/runtime/composables/useFeatureFlagVariantKey.ts new file mode 100644 index 0000000000..1578fb75cb --- /dev/null +++ b/packages/nuxt/src/runtime/composables/useFeatureFlagVariantKey.ts @@ -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(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 +} diff --git a/packages/nuxt/src/runtime/composables/usePostHog.ts b/packages/nuxt/src/runtime/composables/usePostHog.ts new file mode 100644 index 0000000000..dc32183423 --- /dev/null +++ b/packages/nuxt/src/runtime/composables/usePostHog.ts @@ -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 { + const { $posthog } = useNuxtApp() + return $posthog() +} From 890df5edf3a4b213ee50445244b0365dc51af222 Mon Sep 17 00:00:00 2001 From: Geferon Date: Fri, 14 Nov 2025 02:27:32 +0100 Subject: [PATCH 2/4] fix(nuxt): Fixed SSR rendering error with `usePostHog` --- packages/nuxt/src/runtime/composables/usePostHog.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nuxt/src/runtime/composables/usePostHog.ts b/packages/nuxt/src/runtime/composables/usePostHog.ts index dc32183423..ff9f2c3f28 100644 --- a/packages/nuxt/src/runtime/composables/usePostHog.ts +++ b/packages/nuxt/src/runtime/composables/usePostHog.ts @@ -12,7 +12,7 @@ import type posthog from 'posthog-js' * * @returns The PostHog client instance */ -export function usePostHog(): typeof posthog { +export function usePostHog(): typeof posthog | undefined { const { $posthog } = useNuxtApp() - return $posthog() + return ($posthog as () => typeof posthog | undefined)?.() } From f9ce381c8b2ef8c34b474e48dec795d33bb8215e Mon Sep 17 00:00:00 2001 From: Geferon Date: Fri, 14 Nov 2025 02:40:28 +0100 Subject: [PATCH 3/4] fix(nuxt): Fixed wrong method invocation in nuxt example --- examples/example-nuxt/pages/feature-flags.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/example-nuxt/pages/feature-flags.vue b/examples/example-nuxt/pages/feature-flags.vue index 0119734e13..996976991d 100644 --- a/examples/example-nuxt/pages/feature-flags.vue +++ b/examples/example-nuxt/pages/feature-flags.vue @@ -72,6 +72,6 @@ const captureEvent = () => { } const getAllFlags = () => { - allFlags.value = posthog?.getFeatureFlags() + allFlags.value = posthog?.featureFlags.getFlagVariants() } From fe183b8949d11f46fad7cca0965ce25217fda58b Mon Sep 17 00:00:00 2001 From: Geferon Date: Fri, 14 Nov 2025 02:47:03 +0100 Subject: [PATCH 4/4] fix(nuxt): Composables now dispose the listener correctly --- .../src/runtime/composables/useFeatureFlagEnabled.ts | 6 +++--- .../src/runtime/composables/useFeatureFlagPayload.ts | 9 ++++----- .../src/runtime/composables/useFeatureFlagVariantKey.ts | 6 +++--- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/nuxt/src/runtime/composables/useFeatureFlagEnabled.ts b/packages/nuxt/src/runtime/composables/useFeatureFlagEnabled.ts index 5d674e6f77..b8d5d2821a 100644 --- a/packages/nuxt/src/runtime/composables/useFeatureFlagEnabled.ts +++ b/packages/nuxt/src/runtime/composables/useFeatureFlagEnabled.ts @@ -1,4 +1,4 @@ -import { ref, onMounted } from 'vue' +import { ref, onMounted, onUnmounted } from 'vue' import { usePostHog } from './usePostHog' /** @@ -24,11 +24,11 @@ export function useFeatureFlagEnabled(flag: string) { featureEnabled.value = posthog.isFeatureEnabled(flag) }) - return () => { + onUnmounted(() => { if (unsubscribe) { unsubscribe() } - } + }) }) return featureEnabled diff --git a/packages/nuxt/src/runtime/composables/useFeatureFlagPayload.ts b/packages/nuxt/src/runtime/composables/useFeatureFlagPayload.ts index 224d3d85ec..caa6f2a02a 100644 --- a/packages/nuxt/src/runtime/composables/useFeatureFlagPayload.ts +++ b/packages/nuxt/src/runtime/composables/useFeatureFlagPayload.ts @@ -1,4 +1,4 @@ -import { ref, onMounted } from 'vue' +import { ref, onMounted, onUnmounted } from 'vue' import { usePostHog } from './usePostHog' import type { JsonType } from 'posthog-js' @@ -15,8 +15,7 @@ import type { JsonType } from 'posthog-js' */ export function useFeatureFlagPayload(flag: string) { const posthog = usePostHog() - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const featureFlagPayload = ref(posthog?.getFeatureFlagPayload?.(flag)) + const featureFlagPayload = ref(posthog?.getFeatureFlagPayload?.(flag)) onMounted(() => { if (!posthog) return @@ -26,11 +25,11 @@ export function useFeatureFlagPayload(flag: string) { featureFlagPayload.value = posthog.getFeatureFlagPayload(flag) }) - return () => { + onUnmounted(() => { if (unsubscribe) { unsubscribe() } - } + }) }) return featureFlagPayload diff --git a/packages/nuxt/src/runtime/composables/useFeatureFlagVariantKey.ts b/packages/nuxt/src/runtime/composables/useFeatureFlagVariantKey.ts index 1578fb75cb..d54cc8a159 100644 --- a/packages/nuxt/src/runtime/composables/useFeatureFlagVariantKey.ts +++ b/packages/nuxt/src/runtime/composables/useFeatureFlagVariantKey.ts @@ -1,4 +1,4 @@ -import { ref, onMounted } from 'vue' +import { ref, onMounted, onUnmounted } from 'vue' import { usePostHog } from './usePostHog' /** @@ -24,11 +24,11 @@ export function useFeatureFlagVariantKey(flag: string) { featureFlagVariantKey.value = posthog.getFeatureFlag(flag) }) - return () => { + onUnmounted(() => { if (unsubscribe) { unsubscribe() } - } + }) }) return featureFlagVariantKey