Next.js > Real-time updates

Real-time updates

Live updates can be extremely useful both for content editors and regular visitors of your app/website:

  • Content-editors in Draft Mode can see their work-in-progress directly in the production website, without having to refresh the page;

  • Visitors can immediately see new content as it gets published, allowing all kinds of real-time interactions with your website/app (e.g., live-news coverage).

How to use the useQuerySubscription hook

The react-datocms package exposes a useQuerySubscription hook that uses our Real-time Updates API to make any Next.js page update in real-time.

We'll start with the following example, and modify it to activate real-time updates for any visitor of your website:

const PAGE_CONTENT_QUERY = `{
allBlogPosts { id title }
site: _site {
favicon: faviconMetaTags { attributes content tag }
}
}`;
export default async function Page() {
const data = await performRequest(PAGE_CONTENT_QUERY);
return <LatestBlogPosts data={data} />
}

The first step is to build a <RealtimeLatestBlogPosts /> Client component, utilizing the useQuerySubscription hook:

'use client';
import { useQuerySubscription } from 'react-datocms';
function RealtimeLatestBlogPosts({ subscription }) {
const { data, error, status } = useQuerySubscription(subscription);
return <LatestBlogPosts data={data} error={error} status={status} />;
}

Then, in our page component, we can replace the <LatestBlogPosts /> component with <RealtimeLatestBlogPosts />:

export default async function Page() {
const data = await fetchContent();
return (
<RealtimeLatestBlogPosts
subscription={{
query: PAGE_CONTENT_QUERY,
initialData: data,
token: process.env.NEXT_DATOCMS_API_TOKEN,
}}
/>
);
}

Draft Mode + useQuerySubscription

Perhaps a more common scenario is activating real-time updates not for every visitor, but only for content editors in Draft Mode, and also showing records in draft:

In this case, the page component will change a bit, as we need to check draft mode activation and either render <RealtimeLatestBlogPosts /> or <LatestBlogPosts />:

function fetchContent({ includeDrafts }) {
return ;
}
export default async function Page() {
const { isEnabled } = draftMode();
const data = await performRequest(PAGE_CONTENT_QUERY, { includeDrafts: isEnabled });
if (isEnabled) {
return (
<RealtimeLatestBlogPosts
subscription={{
query: PAGE_CONTENT_QUERY,
initialData: data,
environment: process.env.NEXT_DATOCMS_ENVIRONMENT,
token: process.env.NEXT_DATOCMS_API_TOKEN,
}}
/>
);
}
return <LatestBlogPosts data={data} />
}

In summary, the pattern to follow on every page is this:

  1. Do not place the actual content of the page directly inside the Page component, but in a secondary component (ie. <Content />);

  2. Create a real-time wrapper component (ie. <Realtime />) that utilizes the useQuerySubscription hook, and then renders the <Content />;

  3. Create the actual Page component and have it return either <Realtime />, or <Content /> based on whether draft mode is active or not.

DRYing everything up

Repeating this pattern for each page can become repetitive and prone to errors, but it is possible to make the code extremely compact and DRY (Don't Repeat Yourself) by using helper functions that generate both the <Page /> and <Realtime /> components for you. This way, you can focus solely on the <Content /> component, which is what actually contains the content of your page.

To see an example of these helper functions, we recommend you take a look at the code of one of our Next.js Starter Kit — for instance, this is a page component, this is a real-time component, while this is the actual content — but of course, you can customize them as you prefer to best fit them into your project.

If, however, you want to directly see the end result and the experience for editors, we recommend launching the starter from here:

Next.js Starter Kit
Next.js Starter Kit
Publish this demo online with just three clicks in a matter of minutes.
Deploy the demo project