diff --git a/package.json b/package.json
index 0f7a98a..760aec0 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"@threlte/core": "6.0.0-next.11",
"@threlte/extras": "5.0.0-next.16",
"@types/three": "^0.154.0",
- "three": "^0.155.0"
+ "three": "^0.155.0",
+ "web-vitals": "^3.4.0"
}
}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 089d397..213b391 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,6 +17,9 @@ dependencies:
three:
specifier: ^0.155.0
version: 0.155.0
+ web-vitals:
+ specifier: ^3.4.0
+ version: 3.4.0
devDependencies:
'@skeletonlabs/skeleton':
@@ -2340,6 +2343,10 @@ packages:
vite: 4.4.4
dev: true
+ /web-vitals@3.4.0:
+ resolution: {integrity: sha512-n9fZ5/bG1oeDkyxLWyep0eahrNcPDF6bFqoyispt7xkW0xhDzpUBTgyDKqWDi1twT0MgH4HvvqzpUyh0ZxZV4A==}
+ dev: false
+
/webgl-sdf-generator@1.1.1:
resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==}
dev: false
diff --git a/src/lib/components/gltf/Github3d.svelte b/src/lib/components/gltf/Github3d.svelte
index 5e2c117..f0e1d97 100644
--- a/src/lib/components/gltf/Github3d.svelte
+++ b/src/lib/components/gltf/Github3d.svelte
@@ -1,29 +1,29 @@
+
+
- {#await gltf}
-
- {:then gltf}
-
- {:catch error}
-
- {/await}
+ {#await gltf}
+
+ {:then gltf}
+
+ {:catch error}
+
+ {/await}
-
+
diff --git a/src/lib/vitals.ts b/src/lib/vitals.ts
new file mode 100644
index 0000000..b1fcab0
--- /dev/null
+++ b/src/lib/vitals.ts
@@ -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; // 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' 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);
+ }
+}
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index fe95bbc..fbedb34 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -2,11 +2,26 @@
import '../theme.postcss';
import '@skeletonlabs/skeleton/styles/skeleton.css';
import '../app.postcss';
- import { AppShell, Drawer, ProgressBar, drawerStore } from '@skeletonlabs/skeleton';
+ import { AppShell, Drawer } from '@skeletonlabs/skeleton';
import Footer from '../lib/components/Footer.svelte';
import Navigation from '../lib/components/Navigation.svelte';
import Header from '$lib/components/Header.svelte';
+ import { webVitals } from '$lib/vitals';
+ import { browser } from '$app/environment';
+ 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,
+ debug: false
+ });
+ }
+
let routes = [
{ url: '/', label: 'Home' },
{ url: '/projects', label: 'Projects' },
@@ -16,20 +31,17 @@
let progress = 0;
- function handleScroll(event: Event) {
- const { scrollTop, scrollHeight, clientHeight } = event.currentTarget as HTMLElement;
- progress = (scrollTop / (scrollHeight - clientHeight)) * 100;
- }
+ function handleScroll(event: Event) {
+ const { scrollTop, scrollHeight, clientHeight } = event.currentTarget as HTMLElement;
+ progress = (scrollTop / (scrollHeight - clientHeight)) * 100;
+ }
-
+
diff --git a/src/routes/tools/+page.svelte b/src/routes/tools/+page.svelte
index da3e9b0..21076dd 100644
--- a/src/routes/tools/+page.svelte
+++ b/src/routes/tools/+page.svelte
@@ -1,7 +1,7 @@
+
diff --git a/src/routes/tools/Scene.svelte b/src/routes/tools/Scene.svelte
index bb197c7..1fd418d 100644
--- a/src/routes/tools/Scene.svelte
+++ b/src/routes/tools/Scene.svelte
@@ -1,3 +1,4 @@
+