Overview
A headless "new build available" indicator for Next.js. Detects when a newer deployment is live and surfaces that state to your UI — no opinions about how to render it.
Works on any host. Ships first-class support for Vercel deployments.
Install
pnpm add next-build-notifierPeer dependencies: react >= 18, react-dom >= 18, next >= 14 (optional).
Architecture
next-build-notifier is split across three entry points:
next-build-notifier/config(withBuildNotifier) — a Next.js config wrapper that injectsNEXT_PUBLIC_BUILD_IDinto the client bundle at build time so the browser always knows which build it started on.next-build-notifier/server(createVersionRoute) — a Route Handler factory forapp/api/version/route.ts. Returns the current deployment's build id withCache-Control: no-store; addexport const dynamic = 'force-dynamic'in your route file so it renders dynamically rather than freezing the id at build time.next-build-notifier(client) —BuildNotifierProvider,BuildNotifier, anduseBuildNotification. These are Client Components; mount them from a client boundary imported into your server root layout.
Usage
1. Wrap your Next.js config
// next.config.ts
import { withBuildNotifier } from 'next-build-notifier/config';
import type { NextConfig } from 'next';
const config: NextConfig = { /* … */ };
export default withBuildNotifier(config);2. Add the version route handler
// app/api/version/route.ts
import { createVersionRoute } from 'next-build-notifier/server';
export const dynamic = 'force-dynamic';
export const { GET } = createVersionRoute();3. Mount the provider and render (or go headless)
BuildNotifierProvider is a Client Component — wrap it in a client boundary and mount
that from the (server) root layout.
// app/providers.tsx
'use client';
import { BuildNotifierProvider, BuildNotifier } from 'next-build-notifier';
import type { ReactNode } from 'react';
export function Providers({ children }: { children: ReactNode }) {
return (
<BuildNotifierProvider currentBuildId={process.env.NEXT_PUBLIC_BUILD_ID!} intervalMs={60_000}>
{children}
<BuildNotifier>
{(s) =>
s.updateAvailable && !s.dismissed ? (
<button type="button" onClick={s.reload}>
Reload — a new version is available
</button>
) : null
}
</BuildNotifier>
</BuildNotifierProvider>
);
}// app/layout.tsx (Server Component)
import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}currentBuildId is required — pass the build ID baked in by withBuildNotifier
(process.env.NEXT_PUBLIC_BUILD_ID).
Headless — consume state directly
'use client';
import { useBuildNotification } from 'next-build-notifier';
export function UpdateBanner() {
const { updateAvailable, dismissed, reload, dismiss } = useBuildNotification();
if (!updateAvailable || dismissed) return null;
return (
<div role="alert">
New version available.
<button type="button" onClick={reload}>Reload</button>
<button type="button" onClick={dismiss}>Dismiss</button>
</div>
);
}How detection works
- At build time
withBuildNotifiersetsNEXT_PUBLIC_BUILD_IDto the resolved build ID. resolveBuildIddetermines that value using this precedence:NEXT_DEPLOYMENT_ID→VERCEL_DEPLOYMENT_ID→GIT_COMMIT_SHA→VERCEL_GIT_COMMIT_SHA→NEXT_PUBLIC_BUILD_ID→BUILD_ID→ the string literal'development'when none are set.- On the client,
BuildNotifierProviderchecks/api/version(or a custom endpoint) on focus/visibility/reconnect, plus everyintervalMsmilliseconds when an interval is set. - When the returned build ID differs from the one baked into the bundle,
updateAvailableflips totrue.
Vercel
Vercel's Skew Protection pins
framework-level requests (RSC payloads, route prefetches) to the deployment that served
the page. A plain fetch from the browser is not pinned — it resolves against the
current live deployment. The /api/version route leverages this: it always returns the
build ID of whichever deployment is currently receiving traffic, so a mismatch between
that and the baked-in build ID reliably signals a new release.
Self-hosting
On Vercel this works out of the box because VERCEL_DEPLOYMENT_ID is present at both
build time and runtime. When you self-host, you must supply a stable per-deploy identifier
through an env var that is set during both the build and the running server —
GIT_COMMIT_SHA, BUILD_ID, or Next's own NEXT_DEPLOYMENT_ID all work. Without one,
both the baked client id and the /api/version response resolve to 'development', so the
notifier stays permanently inert: it raises no false positives, but it also never detects a
new release.
Configuration
| Prop | Type | Default | Notes |
|---|---|---|---|
currentBuildId | string | — | Required. The build ID baked into the running client (process.env.NEXT_PUBLIC_BUILD_ID). |
intervalMs | number | undefined | Polling interval in ms. A falsy value (0 or undefined) disables the periodic timer; event triggers (focus/visibility/online) still fire. |
endpoint | string | '/api/version' | URL of the version route handler. |
fetcher | function | built-in | Custom async function that resolves to a VersionResponse. |
Dependencies
This package is intentionally dependency-free — it uses only React, the browser's fetch,
and standard Web APIs.
In this section
- Overview — this page
- Changelog — release history