diff --git a/.github/workflows/feature.yml b/.github/workflows/feature.yml
index 6102d650d..55fe77e52 100644
--- a/.github/workflows/feature.yml
+++ b/.github/workflows/feature.yml
@@ -33,6 +33,8 @@ jobs:
           echo "WALLET_CONNECT_PROJECT_ID=${{ secrets.WALLET_CONNECT_PROJECT_ID }}" > .env
           echo "ANKR_TOKEN=${{ secrets.ANKR_TOKEN }}" >> .env
           echo "SCREENING_API_URL=${{ secrets.SCREENING_API_URL }}" >> .env
+          echo "DATAPLANE_URL=${{ secrets.DATAPLANE_URL }}" >> .env
+          echo "RUDDER_KEY=${{ secrets.RUDDER_KEY }}" >> .env
 
       - name: Build
         run: |
diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml
index f7156a620..ca87c7be6 100644
--- a/.github/workflows/production.yml
+++ b/.github/workflows/production.yml
@@ -35,6 +35,8 @@ jobs:
           echo "WALLET_CONNECT_PROJECT_ID=${{ secrets.WALLET_CONNECT_PROJECT_ID }}" > .env
           echo "ANKR_TOKEN=${{ secrets.ANKR_TOKEN }}" >> .env
           echo "SCREENING_API_URL=${{ secrets.SCREENING_API_URL }}" >> .env
+          echo "DATAPLANE_URL=${{ secrets.DATAPLANE_URL }}" >> .env
+          echo "RUDDER_KEY=${{ secrets.RUDDER_KEY }}" >> .env
 
       - name: Build
         run: |
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 531caaea3..c2e88e574 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -66,6 +66,8 @@ jobs:
           echo "WALLET_CONNECT_PROJECT_ID=${{ secrets.WALLET_CONNECT_PROJECT_ID }}" > .env
           echo "ANKR_TOKEN=${{ secrets.ANKR_TOKEN }}" >> .env
           echo "SCREENING_API_URL=${{ secrets.SCREENING_API_URL }}" >> .env
+          echo "DATAPLANE_URL=${{ secrets.DATAPLANE_URL }}" >> .env
+          echo "RUDDER_KEY=${{ secrets.RUDDER_KEY }}" >> .env
 
       - name: Build
         run: |
diff --git a/app.vue b/app.vue
index 70609722e..3768b4615 100644
--- a/app.vue
+++ b/app.vue
@@ -4,6 +4,18 @@
   </NuxtLayout>
 </template>
 
-<script lang="ts" setup></script>
+<script lang="ts" setup>
+import { watch } from "vue";
+import { useRoute } from "vue-router";
 
-<style lang="scss"></style>
+import { trackPage } from "@/utils/analytics";
+
+const route = useRoute();
+watch(
+  () => route.path,
+  () => {
+    trackPage();
+  },
+  { immediate: true }
+);
+</script>
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 799c507ed..0b7301ad4 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -40,6 +40,11 @@ export default defineNuxtConfig({
         {
           src: "https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit",
         },
+        {
+          hid: "Rudder-JS",
+          src: "https://cdn.rudderlabs.com/v1.1/rudder-analytics.min.js",
+          defer: true,
+        },
       ],
     },
   },
@@ -70,6 +75,8 @@ export default defineNuxtConfig({
       nodeType: process.env.NODE_TYPE as undefined | "memory" | "dockerized" | "hyperchain",
       ankrToken: process.env.ANKR_TOKEN,
       screeningApiUrl: process.env.SCREENING_API_URL,
+      dataplaneUrl: process.env.DATAPLANE_URL,
+      rudderKey: process.env.RUDDER_KEY,
     },
   },
   pinia: {
diff --git a/types/index.d.ts b/types/index.d.ts
index c2aaf631d..eefcc3e72 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -103,5 +103,12 @@ declare global {
       ) => string | undefined;
       reset: (widgetId: string) => void;
     };
+    rudderanalytics?: {
+      load: (key: string, url: string) => void;
+      ready: (callback: () => void) => void;
+      page: () => void;
+      track: (eventName: string, params?: unknown) => void;
+      initialized: boolean;
+    };
   }
 }
diff --git a/utils/analytics.ts b/utils/analytics.ts
new file mode 100644
index 000000000..ef733b870
--- /dev/null
+++ b/utils/analytics.ts
@@ -0,0 +1,46 @@
+let analyticsLoaded = false;
+
+const isReady = (): Promise<void> => {
+  return new Promise((resolve, reject) => {
+    if (!window.rudderanalytics) {
+      reject(new Error("Rudder not loaded"));
+    }
+    window.rudderanalytics?.ready(() => {
+      resolve();
+    });
+  });
+};
+
+export async function initAnalytics(): Promise<boolean> {
+  const runtimeConfig = useRuntimeConfig();
+  if (!runtimeConfig.public.rudderKey || !runtimeConfig.public.dataplaneUrl) {
+    return false;
+  }
+
+  if (analyticsLoaded) {
+    await isReady();
+    return true;
+  }
+  await retry(async () => {
+    if (!window.rudderanalytics) {
+      await new Promise((resolve) => setTimeout(resolve, 250));
+      throw new Error("Rudder not loaded");
+    }
+  });
+  window.rudderanalytics?.load(runtimeConfig.public.rudderKey, runtimeConfig.public.dataplaneUrl);
+  analyticsLoaded = true;
+  await isReady();
+  return true;
+}
+
+export async function trackPage(): Promise<void> {
+  if (await initAnalytics()) {
+    window.rudderanalytics?.page();
+  }
+}
+
+export async function trackEvent(eventName: string, params?: unknown): Promise<void> {
+  if (await initAnalytics()) {
+    window.rudderanalytics?.track(eventName, params);
+  }
+}