How to set up surveys in Nuxt
Jan 18, 2024
Surveys are a great way to get feedback from your users. In this guide, we show you how to add a survey to your Nuxt.js app.
We'll create a basic Nuxt app, add PostHog, create a survey, and then show you how to display the survey in the app and get responses.
1. Create a Nuxt app
For this tutorial, we create a basic Nuxt 3
app. First, ensure Node.js is installed (version 18.0.0 or newer). Then run the following command:
npx nuxi@latest init <project-name>
Name it whatever you like (we call ours nuxt-surveys
), select npm
as the package manager, and use the defaults for the remaining options.
Replace the code in app.vue
with a simple heading:
<template><main><h1>Nuxt.js 3 Surveys</h1></main></template>
Run npm run dev
to start your app.
2. Add PostHog
This tutorial shows how to integrate PostHog with
Nuxt 3
. If you're usingNuxt 2
, see our Nuxt docs for how to integrate PostHog.
We use PostHog to create and control our survey as well as monitor results. If you don't have a PostHog instance, you can sign up for free here.
To set it up with your Nuxt app, first install posthog-js
:
npm install posthog-js
Then, add your PostHog API key and host to your nuxt.config.ts
file. You can find your project API key in your PostHog project settings
export default defineNuxtConfig({// ...rest of your configruntimeConfig: {public: {posthogPublicKey: '<ph_project_api_key>',posthogHost: 'https://us.i.posthog.com'}}})
Create a new plugin by first creating a new folder called plugins
and then a new file posthog.client.js
:
mkdir pluginscd pluginstouch posthog.client.js
Add the following code to your posthog.client.js
file:
import { defineNuxtPlugin } from '#app'import posthog from 'posthog-js'export default defineNuxtPlugin(nuxtApp => {const runtimeConfig = useRuntimeConfig();const posthogClient = posthog.init(runtimeConfig.public.posthogPublicKey, {api_host: runtimeConfig.public.posthogHost,})return {provide: {posthog: () => posthogClient}}})
Once you’ve done this, reload your app. You should begin seeing events in the PostHog events explorer.
3. Create a survey
There are two options for displaying a survey using PostHog:
- Use PostHog's prebuilt survey UI.
- Implement your own survey UI.
This tutorial will cover how to implement both options:
Option 1: Use PostHog's prebuilt survey UI
This is the simplest option. PostHog has a variety of survey templates to choose from, and handles all the display logic and response capture for you. You can also customize the questions, branding, and display conditions as needed – see our survey docs for more details on how to do so.
To create a survey with a prebuilt UI, go to the surveys tab in PostHog and click "New survey".
Select any template, or you can create your own by clicking "Create blank survey". Then, configure your survey with the following details:
- Ensure
Presentation
is set to Popover. - Set the display conditions to
All users
. - Use the default values for everything else.
Then, click "Save as draft" and then "Launch". Your survey is now live and you should see it in your app. After submitting responses, you can view results in PostHog.
Option 2: Implement your own survey UI
If you prefer to have complete control of your survey UI and logic, you can still use PostHog to keep track of and analyze your results.
First, create a survey in PostHog like in option 1 above (for this tutorial, we use a Net Promoter Score survey template). The only difference is you must set Presentation
to API.
Then, there are four parts to adding code for our custom survey:
- Create the survey UI.
- Fetch the survey from PostHog.
- Add the logic for displaying and hiding it.
- Capture interactions from it.
1. Create the survey UI
We've created a sample survey UI for this tutorial. To use it, create a new folder components
in your base directory and then create a new file CustomSurvey.vue
in it. Paste the following code in this file:
<template><div class="survey"><h2>{{ title }}</h2><div><button v-for="i in 10" :key="i" class="button" @click="handleSelect(i)">{{ i }}</button></div><div><button class="button" @click="emitDismiss">Dismiss</button><button class="button" @click="emitSubmit">Submit</button></div></div></template><script setup>import { ref } from 'vue';const props = defineProps({title: String,onDismiss: Function,onSubmit: Function});const emit = defineEmits(['onDismiss', 'onSubmit']);const selectedValue = ref(null);function handleSelect(value) {selectedValue.value = value;}function emitDismiss() {emit('onDismiss');}function emitSubmit() {emit('onSubmit', selectedValue.value);}</script><style scoped>.survey {position: fixed;bottom: 20px;right: 20px;width: 400px;padding: 20px;background-color: #ffffff;box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);border-radius: 5px;z-index: 1000;}.button {margin: 5px;padding: 5px;}</style>
Then, integrate the component into app.vue
:
<template><main><div class="App"><h1>Nuxt.js 3 Surveys</h1><CustomSurveyv-if="showSurvey":title="surveyTitle"@onDismiss="handleDismiss"@onSubmit="handleSubmit"/></div></main></template><script setup>import { ref } from 'vue';const showSurvey = ref(true);const surveyTitle = ref('Placeholder title')const handleDismiss = () => {showSurvey.value = false;};const handleSubmit = (value) => {console.log("Submitted value:", value);showSurvey.value = false;};</script>
This shows a survey popup every time you visit your app's homepage.
2. Fetch the survey from PostHog
PostHog keeps track of all active surveys for a user (this is especially helpful if you set up custom display conditions).
To fetch the active surveys when the app is mounted, we call $posthog().getActiveMatchingSurveys()
. This returns a surveys object that looks like this:
[{"id": "018cfcd5-107e-0000-49a1-8e7c6b825947","name": "Net promoter score (NPS) API Survey","description": "","type": "api","linked_flag_key": null,"targeting_flag_key": null,"questions": [{"type": "rating","scale": 10,"display": "number","question": "How likely are you to recommend us to a friend?","description": "","lowerBoundLabel": "Unlikely","upperBoundLabel": "Very likely"}],"conditions": null,"start_date": "2024-01-12T08:41:20.614000Z","end_date": null}]
We can use this survey object to configure our CustomSurvey
component:
<template><!-- ... rest of your template ... --></template><script setup>import { ref, onMounted } from 'vue';import { useNuxtApp } from '#app';const showSurvey = ref(true);const surveyTitle = ref('');const surveyID = ref('');onMounted(async () => {await fetchActiveSurveys();});const fetchActiveSurveys = () => {const { $posthog } = useNuxtApp();$posthog().getActiveMatchingSurveys((surveys) => {if (surveys.length > 0) {const survey = surveys[0];surveyID.value = survey.id;surveyTitle.value = survey.questions[0].question;}});};// rest of your code</script>
3. Add the logic for displaying and hiding it.
We want to make sure we don't show the survey again to users who have either submitted or dismissed it. We use localStorage
to store this data and use it to check whether to show the survey or not.
<template><!-- ... rest of your template ... --></template><script setup>import { ref, onMounted } from 'vue';import { useNuxtApp } from '#app';const showSurvey = ref(false); // updated to false// rest of your codeconst checkSurveyInteraction = () => {const hasInteractedWithSurvey = localStorage.getItem(`hasInteractedWithSurvey_${surveyID.value}`);showSurvey.value = !hasInteractedWithSurvey;};onMounted(async () => {await fetchActiveSurveys();checkSurveyInteraction();});const fetchActiveSurveys = () => {const { $posthog } = useNuxtApp();$posthog().getActiveMatchingSurveys((surveys) => {if (surveys.length > 0) {const survey = surveys[0];surveyID.value = survey.id;surveyTitle.value = survey.questions[0].question;checkSurveyInteraction();}});};const handleDismiss = () => {showSurvey.value = false;localStorage.setItem(`hasInteractedWithSurvey_${surveyID.value}`, 'true');};const handleSubmit = (value) => {showSurvey.value = false;localStorage.setItem(`hasInteractedWithSurvey_${surveyID.value}`, 'true');};</script>
4. Capture interactions from it.
The final step in setting up our survey is capturing interactions. This enables us to analyze the results in PostHog.
There are 3 events to capture:
"survey shown"
"survey dismissed"
"survey sent"
(for responses)
You can capture these events using this.$posthog.capture()
:
<template><!-- ... rest of your template ... --></template><script setup>// rest of your codeconst fetchActiveSurveys = () => {const { $posthog } = useNuxtApp();$posthog().getActiveMatchingSurveys((surveys) => {if (surveys.length > 0) {const survey = surveys[0];surveyID.value = survey.id;surveyTitle.value = survey.questions[0].question;checkSurveyInteraction();if (showSurvey) {$posthog().capture("survey shown", {$survey_id: surveyID // required})}}});};const handleDismiss = () => {showSurvey.value = false;localStorage.setItem(`hasInteractedWithSurvey_${surveyID}`, 'true');const { $posthog } = useNuxtApp();$posthog().capture("survey dismissed", {$survey_id: surveyID // required});};const handleSubmit = (value) => {showSurvey.value = false;localStorage.setItem(`hasInteractedWithSurvey_${surveyID}`, 'true');const { $posthog } = useNuxtApp();$posthog().capture("survey sent", {$survey_id: surveyID, // required$survey_response: value // required});};</script>
Altogether, your code should look like this:
<template><main><div class="App"><h1>Nuxt.js 3 Surveys</h1><CustomSurveyv-if="showSurvey":title="surveyTitle"@onDismiss="handleDismiss"@onSubmit="handleSubmit"/></div></main></template><script setup>import { ref, onMounted } from 'vue';import { useNuxtApp } from '#app';const showSurvey = ref(false); // changed to falseconst surveyTitle = ref('');const surveyID = ref('');const checkSurveyInteraction = () => {const hasInteractedWithSurvey = localStorage.getItem(`hasInteractedWithSurvey_${surveyID.value}`);showSurvey.value = !hasInteractedWithSurvey;};onMounted(async () => {await fetchActiveSurveys();checkSurveyInteraction();});const fetchActiveSurveys = () => {const { $posthog } = useNuxtApp();$posthog().getActiveMatchingSurveys((surveys) => {if (surveys.length > 0) {const survey = surveys[0];surveyID.value = survey.id;surveyTitle.value = survey.questions[0].question;checkSurveyInteraction();if (showSurvey) {$posthog().capture("survey shown", {$survey_id: surveyID // required})}}});};const handleDismiss = () => {showSurvey.value = false;localStorage.setItem(`hasInteractedWithSurvey_${surveyID.value}`, 'true');const { $posthog } = useNuxtApp();$posthog().capture("survey dismissed", {$survey_id: surveyID // required});};const handleSubmit = (value) => {showSurvey.value = false;localStorage.setItem(`hasInteractedWithSurvey_${surveyID.value}`, 'true');const { $posthog } = useNuxtApp();$posthog().capture("survey sent", {$survey_id: surveyID, // required$survey_response: value // required});};</script>
Our survey is now ready to go! The next step is ship the changes, get responses, and view your results.
4. View results
After interacting with your survey, you can view results by selecting the survey from the surveys tab. You'll see data on:
- How many users have seen the survey.
- How many users have dismissed the survey.
- Responses.
If you capture identified events, you can also filter these results based on person properties, cohorts, feature flags and more.
Further reading
Subscribe to our newsletter
Product for Engineers
Read by 25,000+ founders and builders.
We'll share your email with Substack