The Problem
Upgraded a client site from Next.js 15.5 to 16.2 last week, deployed to Vercel, and within a day the Speed Insights dashboard went from a healthy CWV stream to "No data yet." The site was getting traffic. RUM just stopped reporting. No errors, no console warnings, <SpeedInsights /> still in the layout. The numbers were lying.
If your Vercel Speed Insights stopped collecting after a Next.js 16 upgrade, after switching the layout to "use cache", or after tightening your CSP, three different failure modes look identical in the dashboard. Here is how to tell them apart.
Why It Happens
Speed Insights injects a small script (/_vercel/insights/script.js) that collects web-vitals measurements and POSTs them to /_vercel/insights/vitals. The script sits behind Vercel's proxy and is rewritten to your domain so requests stay first-party. Three things break that pipeline on a Next.js 16 upgrade.
Thing 1: The @vercel/speed-insights package version drifts behind Next.js. The 1.0.x series targeted React 18 and the Next.js 15 router. With Next.js 16's React 19.2 and Server Component cache directives, the older package emits a "use client" boundary that breaks Turbopack tree-shaking. The component renders, but production builds ship an empty stub: no script tag, no beacon, no data.
Thing 2: A CSP that does not include the beacon path. Next.js 16 ships a proxy.ts template with a stricter connect-src. If you copied that template, the same-origin beacon works, but on a custom domain with a separate insights endpoint the POST gets silently blocked. You get a CSP report and no telemetry.
Thing 3: "use cache" caches the script tag's nonce. If your root layout opens with "use cache" and renders <SpeedInsights /> inline, the <script> tag carries a build-time nonce. CSP-validating browsers reject it because the nonce does not match the per-request CSP header. The script never runs, the beacon never fires.
The Fix
Step 1: Bump the package and confirm the beacon loads. Update to a Next.js 16-compatible version:
npm install @vercel/speed-insights@latest
You want at least 1.2.0. After deploying, open a real production page in a fresh tab, switch to DevTools Network, filter by insights, and reload. You should see two requests:
GET /_vercel/insights/script.jswith status 200POST /_vercel/insights/vitalsafter a moment of interaction (or onpagehide)
If script.js is missing, the component is not rendering on the server. If script.js is there but vitals never POSTs, your CSP or an ad blocker is the cause.
Step 2: Move <SpeedInsights /> out of any cached segment. The component must render fresh on every request. The cleanest pattern in Next.js 16 is to render it from a small client component that lives inside your layout, but outside any segment marked "use cache":
// app/components/insights.tsx
'use client'
import { SpeedInsights } from '@vercel/speed-insights/next'
export function Insights() {
return <SpeedInsights />
}
Then in your root layout:
// app/layout.tsx
import { Insights } from '@/app/components/insights'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Insights />
</body>
</html>
)
}
If your root layout uses "use cache", do not put it on the same file as <Insights />. Lift the cached part into a child segment.
Step 3: Open the CSP for the beacon. If you have a proxy.ts adding CSP headers, your connect-src and script-src need to include the Vercel insights paths. The minimum addition:
// app/proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function proxy(request: NextRequest) {
const nonce = crypto.randomUUID()
const csp = [
`default-src 'self'`,
`script-src 'self' 'nonce-${nonce}' https://va.vercel-scripts.com`,
`connect-src 'self' https://vitals.vercel-insights.com`,
`img-src 'self' data: blob:`,
`style-src 'self' 'unsafe-inline'`,
].join('; ')
const response = NextResponse.next()
response.headers.set('Content-Security-Policy', csp)
response.headers.set('x-nonce', nonce)
return response
}
export const config = {
matcher: '/((?!_next/static|_next/image|favicon.ico).*)',
}
The two domains that matter are va.vercel-scripts.com (script source) and vitals.vercel-insights.com (beacon target). EU-region projects may resolve to a Frankfurt subdomain, so confirm the host in the Network tab before pinning your CSP.
Step 4: Verify with the debug query string. Append ?vercel-speed-insights-debug=1 to any production URL and reload. The script then logs every metric to the console. If you see metric: { name: 'LCP', value: 1234, ... } lines, telemetry is firing and the dashboard will catch up within an hour. If the console is silent, the script never executed (Step 2 or Step 3 is wrong).
The Vercel Speed Insights docs document the supported frameworks and the exact CSP allowlist if you need to lock it down further.
The Lesson
A silent Speed Insights dashboard after a Next.js 16 upgrade is almost never a Vercel-side issue. It is usually a stale package, a CSP missed during the proxy migration, or a "use cache" directive sitting too close to the script tag. Check the Network tab first, the debug console second, the CSP last.
If your RUM data is missing right when you need it for a Core Web Vitals push, I run performance audits and CWV recovery work on retainer — see my services. For a related Next.js 16 perf gotcha, the next/font CLS layout shift fix covers another upgrade regression that hit a lot of sites this month.