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

  1. Log in to your Notedis dashboard
  2. Navigate to Sites
  3. Click the Edit button on your site (or create a new one)
  4. 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

  1. Check browser console (F12) for JavaScript errors
  2. Verify site key is correct
  3. Clear cache and hard refresh (Ctrl+F5 or Cmd+Shift+R)
  4. 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 defer attribute
  • 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