diff --git a/package-lock.json b/package-lock.json index 21684b2..22d5895 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "clsx": "^2.1.1", "lucide-react": "^0.503.0", "next": "^15.2.3", + "next-plausible": "^3.12.4", "react": "^19.0.0", "react-dom": "^19.0.0", "react-hook-form": "^7.56.1", @@ -5089,6 +5090,20 @@ } } }, + "node_modules/next-plausible": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/next-plausible/-/next-plausible-3.12.4.tgz", + "integrity": "sha512-cD3+ixJxf8yBYvsideTxqli3fvrB7R4BXcvsNJz8Sm2X1QN039WfiXjCyNWkub4h5++rRs6fHhchUMnOuJokcg==", + "license": "MIT", + "funding": { + "url": "https://github.com/4lejandrito/next-plausible?sponsor=1" + }, + "peerDependencies": { + "next": "^11.1.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 ", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", diff --git a/package.json b/package.json index 12b5769..cb2574a 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "clsx": "^2.1.1", "lucide-react": "^0.503.0", "next": "^15.2.3", + "next-plausible": "^3.12.4", "react": "^19.0.0", "react-dom": "^19.0.0", "react-hook-form": "^7.56.1", diff --git a/src/app/components/web-vitals.tsx b/src/app/components/web-vitals.tsx new file mode 100644 index 0000000..31dfa66 --- /dev/null +++ b/src/app/components/web-vitals.tsx @@ -0,0 +1,77 @@ +"use client"; + +import { usePlausible } from "next-plausible"; +import { useReportWebVitals } from "next/web-vitals"; +interface Metric { + /** + * The name of the metric (in acronym form). + */ + name: "CLS" | "FCP" | "FID" | "INP" | "LCP" | "TTFB"; + + /** + * The current value of the metric. + */ + value: number; + + /** + * The rating as to whether the metric value is within the "good", + * "needs improvement", or "poor" thresholds of the metric. + */ + rating: "good" | "needs-improvement" | "poor"; + + /** + * The delta between the current value and the last-reported value. + * On the first report, `delta` and `value` will always be the same. + */ + delta: number; + + /** + * A unique ID representing this particular metric instance. This ID can + * be used by an analytics tool to dedupe multiple values sent for the same + * metric instance, or to group multiple deltas together and calculate a + * total. It can also be used to differentiate multiple different metric + * instances sent from the same page, which can happen if the page is + * restored from the back/forward cache (in that case new metrics object + * get created). + */ + id: string; + + /** + * Any performance entries relevant to the metric value calculation. + * The array may also be empty if the metric value was not based on any + * entries (e.g. a CLS value of 0 given no layout shifts). + */ + entries: PerformanceEntry[]; + + /** + * The type of navigation. + * + * This will be the value returned by the Navigation Timing API (or + * `undefined` if the browser doesn't support that API), with the following + * exceptions: + * - 'back-forward-cache': for pages that are restored from the bfcache. + * - 'back_forward' is renamed to 'back-forward' for consistency. + * - 'prerender': for pages that were prerendered. + * - 'restore': for pages that were discarded by the browser and then + * restored by the user. + */ + navigationType: + | "navigate" + | "reload" + | "back-forward" + | "back-forward-cache" + | "prerender" + | "restore"; +} + +export function WebVitals() { + const plausible = usePlausible(); + useReportWebVitals((metric: Metric) => { + plausible("web-vitals", { + props: { + [metric.name]: metric.rating, + }, + }); + }); + return <>>; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index f641bb7..c77b041 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,8 @@ import "@/styles/globals.css"; - +import PlausibleProvider from "next-plausible"; import { type Metadata } from "next"; import { Geist } from "next/font/google"; +import { WebVitals } from "./components/web-vitals"; export const metadata: Metadata = { title: @@ -21,7 +22,16 @@ export default function RootLayout({ }: Readonly<{ children: React.ReactNode }>) { return ( -
{children} +