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,
} 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,
}
);
components/palette-provider.tsx
"use client";
import { useSearchParams, usePathname } from "next/navigation";
const PaletteProvider = () => {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
searchParams.forEach((value, key) => {
tag(`router.query.${key}`, value);
});
}, [pathname, searchParams]);
return null;
};
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>
);
}
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 } from "react";
import {
init,
events,
markers,
network,
vitals,
profiler,
paint,
} from "@palette.dev/browser";
const useSyncRouterLocation = () => {
const pathname = usePathname();
const searchParams = useSearchParams();
useEffect(() => {
searchParams.forEach((value, key) => {
tag(`router.query.${key}`, value);
});
}, [pathname, searchParams]);
};
export default function PaletteProvider() {
useSyncRouterLocation();
useEffect(() => {
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,
}
);
}, []);
return null;
}
_app.js
// Import palette before all other imports
import {
init,
tag,
events,
markers,
network,
vitals,
profiler,
paint
} from "@palette.dev/browser";
init({
key: "YOUR_CLIENT_KEY",
// Collect click, network, performance events, and profiles
plugins: [events(), network(), vitals(), markers(), profiler(), paint({ componentPaint: true })],
version: process.env.NEXT_PUBLIC_COMMIT_SHA,
});
// 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,
}
);
// Your other imports...
import { useRouter } from "next/router";
const useSyncRouterLocation = () => {
const { pathname, query } = useRouter();
useEffect(() => {
tag("palette.location", pathname);
Object.entries(query).forEach(([key, value]) => {
tag(`router.query.${key}`, Array.isArray(value) ? value.join(",") : (value ?? ""));
});
}, [pathname, query]);
};
const MyApp = () => {
useSyncRouterLocation();
// ...
};
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. Upload Source Maps
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"
}
}
And ensure that your next.config.js
is set to generate source maps in production:
next.config.js
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
productionBrowserSourceMaps: true,
/* config options here */
};
export default nextConfig;
3. Configure Headers
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
async headers() {
return [
{
source: "/(.*)", // apply to all routes
headers: [
{
key: "Document-Policy",
value: "js-profiling",
},
],
},
];
},
};
module.exports = nextConfig;