feat(docs): add OpenPanel analytics alongside Plausible (#5063)

Track page views (initial load + client-side navigations) with the
@openpanel/web SDK, sending to the self-hosted instance at
insights.lucide.dev. This mirrors what the current Plausible script
tracks; Plausible stays in place for the icon popularity script.

Page views are sent from VitePress' onAfterRouteChange hook (after
nextTick) so the page title is already updated. Tracking only runs in
the browser and only when VITE_OPENPANEL_CLIENT_ID is set at build time.

The SDK is bundled from npm instead of loading op1.js, which EasyPrivacy
blocks; insights.lucide.dev/api/track matches no rule in the current
EasyList/EasyPrivacy, uBlock Origin or Brave lists.


Claude-Session: https://claude.ai/code/session_01AZuxmXLjrs29erwQcePCnX

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Eric Fennis
2026-10-23 16:45:49 +02:00
committed by GitHub
co-authored by Claude
parent 9ad51a07f4
commit 9f20b2b8f9
5 changed files with 204 additions and 52 deletions
+7
View File
@@ -16,6 +16,13 @@ const getHeadConfig = ({
href: 'https://analytics.lucide.dev',
},
],
[
'link',
{
rel: 'preconnect',
href: 'https://insights.lucide.dev',
},
],
[
'script',
{
+6 -2
View File
@@ -1,4 +1,4 @@
import { h } from 'vue';
import { h, nextTick } from 'vue';
import DefaultTheme from 'vitepress/theme';
import './style.css';
import 'virtual:group-icons.css';
@@ -12,6 +12,7 @@ import { CATEGORY_VIEW_CONTEXT, categoryViewContext } from './composables/useCat
import { EXTERNAL_LIBS_CONTEXT, externalLibContext } from './composables/useExternalLibs';
import SideBarNavBefore from './layouts/SideBarNavBefore.vue';
import SnackPlayer from './components/editors/SnackPlayer.vue';
import { op } from './utils/openpanel';
const theme: Partial<Theme> = {
extends: DefaultTheme,
@@ -26,11 +27,14 @@ const theme: Partial<Theme> = {
// 'layout-top': () => h(LayoutTop),
});
},
enhanceApp({ app }) {
enhanceApp({ app, router }) {
app.provide(ICON_STYLE_CONTEXT, iconStyleContext);
app.provide(CATEGORY_VIEW_CONTEXT, categoryViewContext);
app.provide(EXTERNAL_LIBS_CONTEXT, externalLibContext);
app.component('SnackPlayer', SnackPlayer);
// Wait for VitePress to update the document title before tracking the page view.
router.onAfterRouteChange = () => nextTick(() => op?.screenView());
},
};
+19
View File
@@ -0,0 +1,19 @@
import { OpenPanel } from '@openpanel/web';
const clientId = import.meta.env.VITE_OPENPANEL_CLIENT_ID;
/**
* Shared OpenPanel instance, only created in the browser and when a client ID is configured.
* Page views are tracked from the VitePress router (see theme/index.ts) instead of
* `trackScreenViews`, so the page title is already updated when the event is sent.
*/
export const op =
!import.meta.env.SSR && clientId
? new OpenPanel({
clientId,
apiUrl: 'https://insights.lucide.dev/api',
trackScreenViews: false,
trackOutgoingLinks: false,
trackAttributes: false,
})
: undefined;
+1
View File
@@ -62,6 +62,7 @@
"@floating-ui/vue": "^2.0.1",
"@headlessui/vue": "^1.7.23",
"@lucide/vue": "workspace:*",
"@openpanel/web": "^1.4.2",
"@vueuse/components": "^14.4.0",
"@vueuse/core": "^14.4.0",
"@vueuse/integrations": "^14.4.0",