Notifying users about new deployments helps users stay informed about new features and bug fixes, and it can also help improve the user experience by ensuring that users are always using the latest version of the app. In this article, we will discuss how to use Next.js and SWR to notify users about new deployments.
To store a version, we'll save a JSON file containing the timestamp in the public folder. The version will be saved before each build with a prebuild script, so that we can read that file in the app to tell which version the app is using when opened.
// scripts/create-version-json.mjsimport { writeFile } from'fs/promises'asyncfunctioncreateVersionJson() {
const versionJson = {
timestamp: Date.now()
}
const filePath = join(__dirname, '../public/version.json');
try {
awaitwriteFile(filePath, JSON.stringify(versionJson), {
encoding: 'utf8',
flag: 'w+'// open the file for reading and writing, positioning the// stream at the beginning of the file. The file is// created if it does not exist.
})
console.log(`✅ Version timestamp is now ${versionJson.timestamp}`)
} catch (error) {
console.error('❌ Error creating file:\n', error)
}
}
// call the function so that it can be executed as a node scriptcreateVersionJson()
To store the version JSON before every build, we'll need a prebuild script. We'll also add a predev script so that the same notification also works in development.
With this hook we cache the current version from version.json on page load, or as soon as it runs.
Using SWR to watch for new versions
Now let's create another hook to get the latest version from version.json.
Here we'll fetch the latest version.json. When a there is a new version, the timestamp will be different than the immutable one. That way we know the app has a new version.
// lib/useLatestVersion.tsimport useSWR from'swr'exportfunctionuseLatestVersion() {
const { data, isLoading } = useSWR('latestVersion', fetchLatestVersion, {
revalidateOnFocus: true,
revalidateIfStale: true,
revalidateOnReconnect: true,
// we don't need to check on mount as the user has just entered the pagerevalidateOnMount: false
})
return { latestVersion: data, isLoading }
}
asyncfunctionfetchLatestVersion() {
const response = awaitfetch('/version.json', {
headers: {
'cache-control': 'no-cache'
}
})
const version = await response.json()
return version.timestampasnumber
}
Now that we have both versions, we can compare them to check whether the app has a new version. As a proof of concept, let's show a button to the user to reload the page whenever the versions differs. That way the user will get the latest and greatest version of your app.
// components/NotifyNewVersion.tsxexportdefaultfunctionNotifyNewVersion() {
const { latestVersion, isLoading: isLoadingLatest } = useLatestVersion()
const { pageLoadVersion, isLoading: isLoadingPageLoad } = usePageLoadVersion()
const isLoading = isLoadingLatest || isLoadingPageLoad
if (isLoading) {
return<span>Loading...</span>
}
// latestVersion and pageLoadVersion will be undefined while loading,// so we check if both are truthy before comparing their valuesconst hasNewVersion = Boolean(latestVersion && pageLoadVersion)
? latestVersion !== pageLoadVersion
: falseif(!hasNewVersion) {
returnnull
}
return (
<><span>
The app has a new version!
</span><buttononClick={() => window.location.reload()}
type="button"
>
Click here to update
</button></>
)
}
Check the source code here. It differs a bit from the code shown above so that the example app makes more sense, but the functionality is the same.
Cool, but where should I put it?
Now that we have the hooks and the component, it's just a matter of where do you want to notify your users.
You may want to notify only signed-in users if your app requires authentication, or only dynamic pages if you have certain pages that are fully static. In those cases you could put the component directly in the page:
You can also use the new Next.js layouts feature to share the component between multiple pages. For that to work we'll need to make a little change in the NotifyNewVersion component to indicate it's a client component:
'use client'// <-- just add this line// ... the rest of the component stays the same as before:exportdefaultfunctionNotifyNewVersion() {
const { latestVersion, isLoading: isLoadingLatest } = useLatestVersion()
const { pageLoadVersion, isLoading: isLoadingPageLoad } = usePageLoadVersion()
const isLoading = isLoadingLatest || isLoadingPageLoad
if (isLoading) {
return<span>Loading...</span>
}
// latestVersion and pageLoadVersion will be undefined while loading,// so we check if both are truthy before comparing their valuesconst hasNewVersion = Boolean(latestVersion && pageLoadVersion)
? latestVersion !== pageLoadVersion
: falseif(!hasNewVersion) {
returnnull
}
return (
<><span>
The app has a new version!
</span><buttononClick={() => window.location.reload()}
type="button"
>
Click here to update
</button></>
)
}
Then you can also use that component in your layout or any other server component that's under the app directory:
// app/app/nested/layout.tsx -- `/app/app` is duplicated in order to have `/app` in the route and easily indicate where you are while testing thisimporttype { ReactNode } from'react'importNotifyNewVersionfrom'../../../components/NotifyNewVersion'exportdefaultfunctionNestedRouteLayout({ children }: { children: ReactNode }) {
return (
<>
{children}
<NotifyNewVersion /></>
)
}
In order to avoid having to always check for the versions and its loading states as we did in the NotifyNewVersion component, we can also abstract hasNewVersion as a reusable hook useHasNewVersion. It will also be useful if you need to check the app version in more than one place. SWR makes this easy:
Then the component can just use its value to know what to render:
// components/NotifyNewVersionWithHook.tsxexportdefaultfunctionNotifyNewVersionWithHook() {
const { hasNewVersion, isLoading } = useHasNewVersion()
if (!hasNewVersion)
returnnullreturn (
<><spanclassName={styles.title}>There is a new version!</span><buttonclassName={styles.button}onClick={() => window.location.reload()}
>
Click here to update
</button></>
)
}
That's it! In this article we provided a way to notify users about new deployments with Next.js and SWR with a few hooks, a script and components to illustrate its usage. Now you can take it and use as you need as the code should be very easy to reuse in any Next.js project.