mirror of
https://github.com/bartvdbraak/hellob.art.git
synced 2025-04-27 17:41:21 +00:00
feat: added vercel web vitals implementation
This commit is contained in:
parent
f84989021c
commit
5df4d20ee1
5 changed files with 112 additions and 9 deletions
|
@ -37,6 +37,7 @@
|
||||||
"@threlte/core": "6.0.0-next.11",
|
"@threlte/core": "6.0.0-next.11",
|
||||||
"@threlte/extras": "5.0.0-next.16",
|
"@threlte/extras": "5.0.0-next.16",
|
||||||
"@types/three": "^0.154.0",
|
"@types/three": "^0.154.0",
|
||||||
"three": "^0.155.0"
|
"three": "^0.155.0",
|
||||||
|
"web-vitals": "^3.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,9 @@ dependencies:
|
||||||
three:
|
three:
|
||||||
specifier: ^0.155.0
|
specifier: ^0.155.0
|
||||||
version: 0.155.0
|
version: 0.155.0
|
||||||
|
web-vitals:
|
||||||
|
specifier: ^3.4.0
|
||||||
|
version: 3.4.0
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@skeletonlabs/skeleton':
|
'@skeletonlabs/skeleton':
|
||||||
|
@ -2340,6 +2343,10 @@ packages:
|
||||||
vite: 4.4.4
|
vite: 4.4.4
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/web-vitals@3.4.0:
|
||||||
|
resolution: {integrity: sha512-n9fZ5/bG1oeDkyxLWyep0eahrNcPDF6bFqoyispt7xkW0xhDzpUBTgyDKqWDi1twT0MgH4HvvqzpUyh0ZxZV4A==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/webgl-sdf-generator@1.1.1:
|
/webgl-sdf-generator@1.1.1:
|
||||||
resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==}
|
resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
81
src/lib/vitals.ts
Normal file
81
src/lib/vitals.ts
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import type { Metric } from 'web-vitals';
|
||||||
|
import { getCLS, getFCP, getFID, getLCP, getTTFB } from 'web-vitals';
|
||||||
|
|
||||||
|
const vitalsUrl = 'https://vitals.vercel-analytics.com/v1/vitals';
|
||||||
|
|
||||||
|
// Improve type safety by defining the navigator.connection type
|
||||||
|
interface NavigatorWithConnection extends Navigator {
|
||||||
|
connection: {
|
||||||
|
effectiveType: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
type Params = Record<string, any>; // Define a type for 'params'
|
||||||
|
|
||||||
|
function getConnectionSpeed() {
|
||||||
|
return 'connection' in navigator && 'connection' && 'effectiveType' in (navigator as NavigatorWithConnection).connection
|
||||||
|
? (navigator as NavigatorWithConnection).connection.effectiveType
|
||||||
|
: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendToAnalytics(metric: Metric, options: {
|
||||||
|
params: Params;
|
||||||
|
path: string;
|
||||||
|
analyticsId: string;
|
||||||
|
debug: boolean;
|
||||||
|
}) {
|
||||||
|
const page = Object.entries(options.params).reduce(
|
||||||
|
(acc, [key, value]) => acc.replace(value, `[${key}]`),
|
||||||
|
options.path
|
||||||
|
);
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
dsn: options.analyticsId,
|
||||||
|
id: metric.id,
|
||||||
|
page,
|
||||||
|
href: location.href,
|
||||||
|
event_name: metric.name,
|
||||||
|
value: metric.value.toString(),
|
||||||
|
speed: getConnectionSpeed(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.debug) {
|
||||||
|
console.log('[Web Vitals]', metric.name, JSON.stringify(body, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize body to a URLSearchParams object
|
||||||
|
const searchParams = new URLSearchParams(body);
|
||||||
|
|
||||||
|
// The type 'Record<string, string>' is compatible with 'URLSearchParams'
|
||||||
|
const blob = new Blob([searchParams.toString()], {
|
||||||
|
type: 'application/x-www-form-urlencoded',
|
||||||
|
});
|
||||||
|
if (navigator.sendBeacon) {
|
||||||
|
navigator.sendBeacon(vitalsUrl, blob);
|
||||||
|
} else {
|
||||||
|
fetch(vitalsUrl, {
|
||||||
|
body: blob,
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'omit',
|
||||||
|
keepalive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function webVitals(options: {
|
||||||
|
params: Params; // Use the defined 'Params' type here
|
||||||
|
path: string;
|
||||||
|
analyticsId: string;
|
||||||
|
debug: boolean;
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
getFID((metric) => sendToAnalytics(metric, options));
|
||||||
|
getTTFB((metric) => sendToAnalytics(metric, options));
|
||||||
|
getLCP((metric) => sendToAnalytics(metric, options));
|
||||||
|
getCLS((metric) => sendToAnalytics(metric, options));
|
||||||
|
getFCP((metric) => sendToAnalytics(metric, options));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Web Vitals]', err);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,20 @@
|
||||||
import Navigation from '../lib/components/Navigation.svelte';
|
import Navigation from '../lib/components/Navigation.svelte';
|
||||||
import Header from '$lib/components/Header.svelte';
|
import Header from '$lib/components/Header.svelte';
|
||||||
|
|
||||||
|
import { webVitals } from '$lib/vitals';
|
||||||
|
import { browser } from '$app/env';
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
|
let analyticsId = import.meta.env.VERCEL_ANALYTICS_ID;
|
||||||
|
|
||||||
|
$: if (browser && analyticsId) {
|
||||||
|
webVitals({
|
||||||
|
path: $page.url.pathname,
|
||||||
|
params: $page.params,
|
||||||
|
analyticsId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
let routes = [
|
let routes = [
|
||||||
{ url: '/', label: 'Home' },
|
{ url: '/', label: 'Home' },
|
||||||
{ url: '/projects', label: 'Projects' },
|
{ url: '/projects', label: 'Projects' },
|
||||||
|
@ -16,20 +30,17 @@
|
||||||
|
|
||||||
let progress = 0;
|
let progress = 0;
|
||||||
|
|
||||||
function handleScroll(event: Event) {
|
function handleScroll(event: Event) {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget as HTMLElement;
|
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget as HTMLElement;
|
||||||
progress = (scrollTop / (scrollHeight - clientHeight)) * 100;
|
progress = (scrollTop / (scrollHeight - clientHeight)) * 100;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Drawer width="w-[280px] md:w-[480px]">
|
<Drawer width="w-[280px] md:w-[480px]">
|
||||||
<Navigation {routes} />
|
<Navigation {routes} />
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
|
||||||
<AppShell
|
<AppShell slotSidebarLeft="w-0 md:w-40" on:scroll={handleScroll}>
|
||||||
slotSidebarLeft="w-0 md:w-40"
|
|
||||||
on:scroll={handleScroll}
|
|
||||||
>
|
|
||||||
<svelte:fragment slot="header">
|
<svelte:fragment slot="header">
|
||||||
<Header {progress} />
|
<Header {progress} />
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
|
@ -5,5 +5,8 @@ export default defineConfig({
|
||||||
plugins: [sveltekit()],
|
plugins: [sveltekit()],
|
||||||
ssr: {
|
ssr: {
|
||||||
noExternal: ['three']
|
noExternal: ['three']
|
||||||
|
},
|
||||||
|
define: {
|
||||||
|
'import.meta.env.VERCEL_ANALYTICS_ID': JSON.stringify(process.env.VERCEL_ANALYTICS_ID)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue