Next.js/Vercel new deployment notification.

Does anyone know how the update notification/toast on T3 chat is implemented? By default when there's new update the page just automatically refreshes. I would like to display the update toast to the users with the refresh button so that they can manually refresh the page instead of it refreshing automatically.
8 Replies
Nate
Nate4w ago
Sonner
An opinionated toast component for React.
Nate
Nate4w ago
lmk if you need help with the logic, or if your app isn’t react but yeah this or the more-deprecated toast component from shadcn is most certainly what they are using (please @ me or reply)
Megumin
MeguminOP4w ago
Oh I use Sonner as well, i didn’t mean the toast itself, I meant the logic that fires up the toast. So far i managed to implement it with service workers.
Nate
Nate4w ago
so previously when there was a new page the users’ pages would refresh automatically? and you weren’t doing that yourself?
Megumin
MeguminOP4w ago
Yes, that’s how vercel seams to working by default. Like any deployment update would trigger a refresh
Nate
Nate4w ago
Huh. I had no idea that was a thing. How’d you do it yourself?
Megumin
MeguminOP4w ago
I just used augment ai, it took a whole day of trial and error, honestly i should have just gone and read some documentation and figure it out myself but i was too lazy I’ll add the code here tomorrow so that anyone can use it if they need it. @Nate Here's the code:
// PATH: public/sw.js
// Service Worker for Update Detection
const CACHE_NAME = "app-cache-v1";
const VERSION = "{{BUILD_ID}}"; // This will be replaced during build

self.addEventListener("install", () => {});

self.addEventListener("activate", (event) => {
event.waitUntil(self.clients.claim());
});

self.addEventListener("message", (event) => {
if (event.data && event.data.type === "SKIP_WAITING") {
self.skipWaiting();
}
});
// PATH: public/sw.js
// Service Worker for Update Detection
const CACHE_NAME = "app-cache-v1";
const VERSION = "{{BUILD_ID}}"; // This will be replaced during build

self.addEventListener("install", () => {});

self.addEventListener("activate", (event) => {
event.waitUntil(self.clients.claim());
});

self.addEventListener("message", (event) => {
if (event.data && event.data.type === "SKIP_WAITING") {
self.skipWaiting();
}
});
// next.config.js
// required variables:
const fs = require("fs");
const path = require("path");
const vercelGitCommitSha = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA;
const vercelDeploymentId =
process.env.VERCEL_DEPLOYMENT_ID ||
process.env.NEXT_PUBLIC_VERCEL_DEPLOYMENT_ID;
/** @type {import('next').NextConfig} */
// config option method:
generateBuildId: async () => {
if (
process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ||
process.env.CI ||
process.env.VERCEL
) {
let newVersion;

if (vercelGitCommitSha) {
// Use Git commit SHA for Vercel deployments
newVersion = `vercel-${vercelGitCommitSha}`;
} else if (vercelDeploymentId) {
// Use deployment ID as fallback
newVersion = `vercel-${vercelDeploymentId}`;
} else {
// Local development fallback
newVersion = `build-${Date.now()}`;
}

// Replace BUILD_ID placeholder in service worker
const swPath = path.join(process.cwd(), "public", "sw.js");

try {
// Read the current service worker file
let swContent = fs.readFileSync(swPath, "utf8");

// Replace the VERSION constant with the new version
swContent = swContent.replace(/\{\{BUILD_ID}}/g, newVersion);

// Write the updated content back to the file
fs.writeFileSync(swPath, swContent, "utf8");

// console.log(`✅ Service worker version updated to: ${newVersion}`);
} catch (error) {
console.error("❌ Error updating service worker version:", error);
process.exit(1);
}
}

return (
process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA || `build-${Date.now()}`
);
},
// next.config.js
// required variables:
const fs = require("fs");
const path = require("path");
const vercelGitCommitSha = process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA;
const vercelDeploymentId =
process.env.VERCEL_DEPLOYMENT_ID ||
process.env.NEXT_PUBLIC_VERCEL_DEPLOYMENT_ID;
/** @type {import('next').NextConfig} */
// config option method:
generateBuildId: async () => {
if (
process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA ||
process.env.CI ||
process.env.VERCEL
) {
let newVersion;

if (vercelGitCommitSha) {
// Use Git commit SHA for Vercel deployments
newVersion = `vercel-${vercelGitCommitSha}`;
} else if (vercelDeploymentId) {
// Use deployment ID as fallback
newVersion = `vercel-${vercelDeploymentId}`;
} else {
// Local development fallback
newVersion = `build-${Date.now()}`;
}

// Replace BUILD_ID placeholder in service worker
const swPath = path.join(process.cwd(), "public", "sw.js");

try {
// Read the current service worker file
let swContent = fs.readFileSync(swPath, "utf8");

// Replace the VERSION constant with the new version
swContent = swContent.replace(/\{\{BUILD_ID}}/g, newVersion);

// Write the updated content back to the file
fs.writeFileSync(swPath, swContent, "utf8");

// console.log(`✅ Service worker version updated to: ${newVersion}`);
} catch (error) {
console.error("❌ Error updating service worker version:", error);
process.exit(1);
}
}

return (
process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA || `build-${Date.now()}`
);
},
So essentaily how this works is: The UpdateNotification will register the service worker and check if the service worker file updated (sw.js file). If it updated it will display the notification. The generateBuildId method updates the sw.js file with a new build id every time a new build is created, but only on vercel since we don't want to change the file locally. This way you don't need server code that would notify the client that there's an update. It's a static file so it shouldn't count towards the monthly cost.
Megumin
MeguminOP4w ago
new UpdateNotification.tsx code, the previous one kept displaying the toast if you manually reloaded. It would only go away if you pressed the refresh button on the toast.

Did you find this page helpful?