Turbopack
1. Initialize Client
- Next.js >=15.3
- Next.js (App Router)
- Next.js (Pages Router)
instrumentation-client.ts
import {
init,
events,
markers,
network,
vitals,
profiler,
paint,
tag,
} from "@palette.dev/browser";
init({
key: process.env.NEXT_PUBLIC_PALETTE_CLIENT_KEY!,
plugins: [
events(),
network(),
vitals(),
markers(),
profiler(),
paint({ componentPaint: true }),
],
});
// Start profiler on user interactions
profiler.on(
[
"paint.click",
"paint.keydown",
"paint.scroll",
"paint.mousemove",
"markers.measure",
"events.load",
"events.dcl",
],
{
sampleInterval: 1,
maxBufferSize: 100_000,
}
);
export function onRouterTransitionStart(url: string) {
tag("palette.location", url);
}
app/layout.tsx
import PaletteProvider from "@/components/palette-provider";
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode; }>) {
return (
<html>
<body>
/* Initialize Palette */
<PaletteProvider/>
{children}
</body>
</html>
);
}
components/palette-provider.tsx
"use client";
import { useSearchParams, usePathname } from "next/navigation";
import { useEffect, Suspense } from "react";
import {
init,
events,
markers,
network,
vitals,
profiler,
paint,
tag,
} from "@palette.dev/browser";
const useLocation = () => {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
tag("palette.location", pathname);
searchParams.forEach((value, key) => {
tag(`router.query.${key}`, value);
});
}, [pathname, searchParams]);
};
function PaletteProviderInner() {
useLocation();
useEffect(() => {
init({
key: process.env.NEXT_PUBLIC_PALETTE_CLIENT_KEY!,
plugins: [
events(),
network(),
vitals(),
markers(),
profiler(),
paint({ componentPaint: true }),
],
});
// Start the profiler on the following events:
profiler.on(
[
"paint.click",
"paint.keydown",
"paint.scroll",
"paint.mousemove",
"markers.measure",
"events.load",
"events.dcl",
],
{
sampleInterval: 1,
maxBufferSize: 100_000,
}
);
}, []);
return null;
}
export default function PaletteProvider() {
return (
<Suspense fallback={null}>
<PaletteProviderInner />
</Suspense>
);
}
_app.tsx
import type { AppProps } from "next/app";
import { useRouter } from "next/router";
import { useEffect } from "react";
import {
init,
events,
markers,
network,
vitals,
profiler,
paint,
tag,
} from "@palette.dev/browser";
import "../styles/globals.css";
init({
key: process.env.NEXT_PUBLIC_PALETTE_CLIENT_KEY!,
plugins: [
events(),
network(),
vitals(),
markers(),
profiler(),
paint({ componentPaint: true }),
],
});
// Start the profiler on the following events:
profiler.on(
[
"paint.click",
"paint.keydown",
"paint.scroll",
"paint.mousemove",
"markers.measure",
"events.load",
"events.dcl",
],
{
sampleInterval: 1,
maxBufferSize: 100_000,
}
);
const useLocation = () => {
const { pathname, query } = useRouter();
// Track route query params
useEffect(() => {
tag("palette.location", pathname);
Object.entries(query).forEach(([key, value]) => {
tag(
`router.query.${key}`,
Array.isArray(value) ? value.join(",") : value ?? ""
);
});
}, [pathname, query]);
};
export default function App({ Component, pageProps }: AppProps) {
useLocation();
return <Component {...pageProps} />;
}
Palette requires the version
parameter to be passed to init
when using Turbopack:
init({
// ...
version: process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ?? "local",
});
2. Configure Headers and Source Maps
Ensure your next.config.js
generates source maps in production and has profiling headers:
next.config.js
import { NextConfig } from "next";
/** @type {import('next').NextConfig} */
const nextConfig: NextConfig = {
productionBrowserSourceMaps: true,
async headers() {
return [
{
source: "/(.*)", // apply to all routes
headers: [
{
key: "Document-Policy",
value: "js-profiling",
},
],
},
];
},
};
export default nextConfig;
3. Post-build Source Maps Upload
Use the palette upload
command to upload source maps after building your Next.js application. You can add this command to your package.json
scripts:
package.json
{
"scripts": {
"build": "next build && palette upload .next/static --release"
}
}