Vue.js Installation
Install Notedis feedback widget in your Vue.js application.
Perfect For
- Vue 3
- Vue 2
- Vite + Vue
- Vue CLI
- Nuxt (see separate guide if available)
- Custom Vue setups
Installation Methods
Method 1: Script Tag in index.html (Recommended)
The simplest method - add widget to your public/index.html or index.html file.
Step 1: Get Your Site Key
- Log in to your Notedis dashboard
- Navigate to Sites
- Click the Edit button on your site (or create a new one)
- Copy your Site Key (starts with
site_)
Step 2: Add Widget to index.html
Open public/index.html (Vue CLI) or index.html (Vite) and add before the closing </body> tag:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My Vue App</title>
</head>
<body>
<div id="app"></div>
<!-- Notedis Feedback Widget - Add before </body> -->
<script>
window.notedisConfig = {
siteKey: 'your-unique-site-key-here',
apiUrl: 'https://notedis.com',
position: 'bottom-right',
color: '#3B82F6'
};
</script>
<script src="https://notedis.com/js/widget.js" defer></script>
</body>
</html>
Done! The widget will now appear on all pages.
Method 2: Vue Component (Vue 3)
Create a reusable Vue 3 component for the widget.
Step 1: Create Widget Component
Create src/components/NotedisFeedback.vue:
<template>
<!-- This component doesn't render anything -->
</template>
<script setup>
import { onMounted, onBeforeUnmount } from 'vue';
const props = defineProps({
siteKey: {
type: String,
required: true
},
position: {
type: String,
default: 'bottom-right'
},
color: {
type: String,
default: '#3B82F6'
},
buttonText: {
type: String,
default: null
}
});
let scriptElement = null;
onMounted(() => {
// Set configuration
window.notedisConfig = {
siteKey: props.siteKey,
apiUrl: 'https://notedis.com',
position: props.position,
color: props.color,
...(props.buttonText && { buttonText: props.buttonText })
};
// Load widget script
scriptElement = document.createElement('script');
scriptElement.src = 'https://notedis.com/js/widget.js';
scriptElement.defer = true;
document.body.appendChild(scriptElement);
});
onBeforeUnmount(() => {
// Cleanup on unmount
if (scriptElement && scriptElement.parentNode) {
scriptElement.parentNode.removeChild(scriptElement);
}
// Remove widget button
const widgetButton = document.getElementById('notedis-feedback-button');
if (widgetButton) {
widgetButton.remove();
}
});
</script>
Step 2: Add Component to App
In your main src/App.vue:
<template>
<div id="app">
<!-- Your app content -->
<h1>My Vue App</h1>
<router-view />
<!-- Notedis Feedback Widget -->
<NotedisFeedback
site-key="your-unique-site-key-here"
position="bottom-right"
color="#3B82F6"
/>
</div>
</template>
<script setup>
import NotedisFeedback from './components/NotedisFeedback.vue';
</script>
Method 3: Vue Component (Vue 2 Options API)
For Vue 2 or if you prefer Options API:
Create src/components/NotedisFeedback.vue:
<template>
<!-- This component doesn't render anything -->
</template>
<script>
export default {
name: 'NotedisFeedback',
props: {
siteKey: {
type: String,
required: true
},
position: {
type: String,
default: 'bottom-right'
},
color: {
type: String,
default: '#3B82F6'
},
buttonText: {
type: String,
default: null
}
},
mounted() {
// Set configuration
window.notedisConfig = {
siteKey: this.siteKey,
apiUrl: 'https://notedis.com',
position: this.position,
color: this.color,
...(this.buttonText && { buttonText: this.buttonText })
};
// Load widget script
this.scriptElement = document.createElement('script');
this.scriptElement.src = 'https://notedis.com/js/widget.js';
this.scriptElement.defer = true;
document.body.appendChild(this.scriptElement);
},
beforeDestroy() {
// Cleanup on unmount
if (this.scriptElement && this.scriptElement.parentNode) {
this.scriptElement.parentNode.removeChild(this.scriptElement);
}
// Remove widget button
const widgetButton = document.getElementById('notedis-feedback-button');
if (widgetButton) {
widgetButton.remove();
}
}
};
</script>
TypeScript Support (Vue 3 + TypeScript)
Create src/components/NotedisFeedback.vue:
<template>
<!-- This component doesn't render anything -->
</template>
<script setup lang="ts">
import { onMounted, onBeforeUnmount } from 'vue';
interface Props {
siteKey: string;
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
color?: string;
buttonText?: string;
onOpen?: () => void;
onClose?: () => void;
onSubmit?: (feedback: any) => void;
}
const props = withDefaults(defineProps<Props>(), {
position: 'bottom-right',
color: '#3B82F6',
buttonText: undefined,
onOpen: undefined,
onClose: undefined,
onSubmit: undefined
});
declare global {
interface Window {
notedisConfig?: {
siteKey: string;
apiUrl: string;
position?: string;
color?: string;
buttonText?: string;
onOpen?: () => void;
onClose?: () => void;
onSubmit?: (feedback: any) => void;
};
notedisWidget?: {
open: () => void;
close: () => void;
};
}
}
let scriptElement: HTMLScriptElement | null = null;
onMounted(() => {
// Set configuration
window.notedisConfig = {
siteKey: props.siteKey,
apiUrl: 'https://notedis.com',
position: props.position,
color: props.color,
...(props.buttonText && { buttonText: props.buttonText }),
...(props.onOpen && { onOpen: props.onOpen }),
...(props.onClose && { onClose: props.onClose }),
...(props.onSubmit && { onSubmit: props.onSubmit })
};
// Load widget script
scriptElement = document.createElement('script');
scriptElement.src = 'https://notedis.com/js/widget.js';
scriptElement.defer = true;
document.body.appendChild(scriptElement);
});
onBeforeUnmount(() => {
// Cleanup
if (scriptElement?.parentNode) {
scriptElement.parentNode.removeChild(scriptElement);
}
const widgetButton = document.getElementById('notedis-feedback-button');
if (widgetButton) {
widgetButton.remove();
}
});
</script>
Usage:
<NotedisFeedback
site-key="your-site-key"
position="bottom-right"
color="#3B82F6"
:on-submit="handleFeedbackSubmit"
/>
Environment Variables
Store your site key in environment variables for security.
Step 1: Create .env File
Vite:
Create .env in your project root:
VITE_NOTEDIS_SITE_KEY=your-site-key-here
Vue CLI:
Create .env in your project root:
VUE_APP_NOTEDIS_SITE_KEY=your-site-key-here
Important: Never commit your .env file to version control. Add to .gitignore:
.env
.env.local
Step 2: Use Environment Variable
Vite:
<NotedisFeedback
:site-key="import.meta.env.VITE_NOTEDIS_SITE_KEY"
/>
Vue CLI:
<NotedisFeedback
:site-key="process.env.VUE_APP_NOTEDIS_SITE_KEY"
/>
Step 3: Different Keys for Dev/Prod
Vite:
# .env.development
VITE_NOTEDIS_SITE_KEY=site_dev_key
# .env.production
VITE_NOTEDIS_SITE_KEY=site_prod_key
Vue CLI:
# .env.development
VUE_APP_NOTEDIS_SITE_KEY=site_dev_key
# .env.production
VUE_APP_NOTEDIS_SITE_KEY=site_prod_key
Custom Trigger Button
Hide the default button and use your own custom trigger.
<template>
<div>
<button
@click="openFeedback"
class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"
>
Send Feedback
</button>
</div>
</template>
<script setup>
import { onMounted } from 'vue';
onMounted(() => {
window.notedisConfig = {
siteKey: 'your-site-key',
apiUrl: 'https://notedis.com',
hideButton: true // Hide default button
};
const script = document.createElement('script');
script.src = 'https://notedis.com/js/widget.js';
script.defer = true;
document.body.appendChild(script);
});
const openFeedback = () => {
if (window.notedisWidget) {
window.notedisWidget.open();
}
};
</script>
Vue Router Integration
Widget works seamlessly with Vue Router - button persists across route changes.
<template>
<div id="app">
<NotedisFeedback site-key="your-site-key" />
<nav>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-link to="/contact">Contact</router-link>
</nav>
<router-view />
</div>
</template>
<script setup>
import NotedisFeedback from './components/NotedisFeedback.vue';
</script>
Conditional Loading
Only Show for Authenticated Users
<template>
<div id="app">
<!-- Only show widget for logged-in users -->
<NotedisFeedback
v-if="isAuthenticated"
site-key="your-site-key"
/>
<!-- Your app content -->
<router-view />
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useStore } from 'vuex'; // or Pinia
import NotedisFeedback from './components/NotedisFeedback.vue';
const store = useStore();
const isAuthenticated = computed(() => store.state.auth.isAuthenticated);
</script>
Only Show in Development
<template>
<div id="app">
<!-- Only show in development -->
<NotedisFeedback
v-if="isDevelopment"
site-key="your-dev-site-key"
color="#ef4444"
button-text="🚧 Dev Feedback"
/>
</div>
</template>
<script setup>
import { computed } from 'vue';
import NotedisFeedback from './components/NotedisFeedback.vue';
const isDevelopment = computed(() => import.meta.env.MODE === 'development');
// Or for Vue CLI: process.env.NODE_ENV === 'development'
</script>
Hide on Specific Routes
<template>
<div id="app">
<!-- Hide on checkout or admin pages -->
<NotedisFeedback
v-if="!hideWidget"
site-key="your-site-key"
/>
</div>
</template>
<script setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import NotedisFeedback from './components/NotedisFeedback.vue';
const route = useRoute();
const hideWidget = computed(() => {
return ['/checkout', '/admin'].some(path =>
route.path.startsWith(path)
);
});
</script>
Composable Approach (Vue 3)
Create a reusable composable for advanced usage.
Create src/composables/useNotedisFeedback.js:
import { onMounted, onBeforeUnmount } from 'vue';
export function useNotedisFeedback(config) {
let scriptElement = null;
onMounted(() => {
// Set configuration
window.notedisConfig = {
apiUrl: 'https://notedis.com',
...config
};
// Load widget script
scriptElement = document.createElement('script');
scriptElement.src = 'https://notedis.com/js/widget.js';
scriptElement.defer = true;
document.body.appendChild(scriptElement);
});
onBeforeUnmount(() => {
// Cleanup
if (scriptElement?.parentNode) {
scriptElement.parentNode.removeChild(scriptElement);
}
const widgetButton = document.getElementById('notedis-feedback-button');
if (widgetButton) {
widgetButton.remove();
}
});
const open = () => {
if (window.notedisWidget) {
window.notedisWidget.open();
}
};
const close = () => {
if (window.notedisWidget) {
window.notedisWidget.close();
}
};
return {
open,
close
};
}
Usage:
<template>
<div>
<button @click="feedbackWidget.open()">
Send Feedback
</button>
</div>
</template>
<script setup>
import { useNotedisFeedback } from '@/composables/useNotedisFeedback';
const feedbackWidget = useNotedisFeedback({
siteKey: 'your-site-key',
position: 'bottom-right',
color: '#3B82F6'
});
</script>
Analytics Integration
Track feedback events with your analytics:
<template>
<!-- Component template -->
</template>
<script setup>
import { onMounted } from 'vue';
onMounted(() => {
window.notedisConfig = {
siteKey: 'your-site-key',
apiUrl: 'https://notedis.com',
onOpen: () => {
// Track with Google Analytics
if (window.gtag) {
window.gtag('event', 'feedback_opened', {
event_category: 'engagement'
});
}
},
onSubmit: (feedback) => {
// Track with Google Analytics
if (window.gtag) {
window.gtag('event', 'feedback_submitted', {
event_category: 'engagement',
category: feedback.category,
priority: feedback.priority
});
}
// You can also emit a custom event
// emit('feedbackSubmitted', feedback);
}
};
const script = document.createElement('script');
script.src = 'https://notedis.com/js/widget.js';
script.defer = true;
document.body.appendChild(script);
});
</script>
Pinia Store Integration
Create a Pinia store for widget state management:
// stores/feedback.js
import { defineStore } from 'pinia';
export const useFeedbackStore = defineStore('feedback', {
state: () => ({
isOpen: false,
feedbackCount: 0
}),
actions: {
openWidget() {
if (window.notedisWidget) {
window.notedisWidget.open();
this.isOpen = true;
}
},
closeWidget() {
if (window.notedisWidget) {
window.notedisWidget.close();
this.isOpen = false;
}
},
incrementFeedbackCount() {
this.feedbackCount++;
}
}
});
Usage:
<template>
<div>
<button @click="feedbackStore.openWidget()">
Send Feedback ({{ feedbackStore.feedbackCount }})
</button>
</div>
</template>
<script setup>
import { useFeedbackStore } from '@/stores/feedback';
const feedbackStore = useFeedbackStore();
</script>
Testing
The widget won't interfere with your tests. However, if you want to mock it:
// tests/setup.js or vitest.setup.js
global.notedisConfig = {};
global.notedisWidget = {
open: vi.fn(),
close: vi.fn()
};
Troubleshooting
Widget doesn't appear
- Check browser console (F12) for JavaScript errors
- Verify site key is correct
- Clear cache and hard refresh (Ctrl+F5 or Cmd+Shift+R)
- Check component mounting - ensure component is rendering
Widget appears multiple times
Cause: Component mounting multiple times or hot reload issues.
Solution: Add a check to prevent duplicate loading:
onMounted(() => {
// Prevent duplicate loading
if (document.querySelector('script[src="https://notedis.com/js/widget.js"]')) {
return;
}
// Load widget...
});
Widget not working with hot reload
Cause: Hot module replacement may not reload widget properly.
Solution: Full page refresh (F5) to reload widget.
Performance Notes
- Widget loads asynchronously with
deferattribute - No impact on initial page load or PageSpeed scores
- Lazy loads only when needed
- Minimal bundle size impact (CDN-based)
- Works seamlessly with Vue's reactivity system
Next Steps
- Configuration - Customize widget appearance and behavior
- Customization - Advanced styling and custom triggers
- Features - Explore all widget features
- Troubleshooting - Common issues and solutions