Back to All Posts

November 5, 2024

Automatic On-Demand Revalidation for Next.js: How It Works

On-demand revalidation, without developer effort.


We recently released automatic on-demand revalidation for Next.js. This tight integration between the basehub SDK and Next.js gives both developers and content editors the best of both worlds:

  • Developers, can provide on-demand revalidation with little-to-no effort.

  • Content editors, will see content update instantly as they commit new changes.

But how exactly does it work, and is it really automatic?

How It Works

Developers query our GraphQL API using basehub, our type-safe SDK, and queries look something like this:

import { basehub } from 'basehub'
import { ctaFragment } from './_components/cta'

const Page = async () => {
  const data = await basehub().query({ 
    homepage: { 
      heroTitle: true,
      cta: ctaFragment 
    } 
  }) 

  // render `data`
  // side note: `<Pump />` uses `basehub()` too under the hood
}

While other CMSes provide just the GraphQL API and leave it to the developer to find a query client, we also provide the client. We do so to provide end-to-end typesafety, a unified DX, and also, to build new features that require tight client/server integration.

This is one of those features.

When basehub().query gets called…

  1. It hashes the input (query + variables).

  2. It uses the hash as a Next.js cache tag.

  3. It sends the query to our API, with the cache tag as a header.

When our GraphQL API receives the query…

  1. It resolves the query and responds as fast as possible.

  2. In the background (waitUntil ftw), it hashes the response and stores stores the cache tag, the raw query, and the response hash (plus some other request metadata) in Postgres.

When a commit happens… (here’s the magic)

  1. Our server will get all of the stored queries and re-run them again, now against the new dataset (the new commit tree).

  2. For each query, we’ll hash the new response and compare it to the one we have stored, and mark it “for revalidation”.

  3. We’ll spin up a headless browser (which probably is already up by the time we get here) and command it to navigate to your website and call our <Toolbar />'s Server Action which will call revalidateTag.

And that’s it!

Developer Setup

The only thing you need to enable this behaviour is to mount the <Toolbar /> somewhere in your app.

// app/layout.tsx (recommended)
import { Toolbar } from "basehub/next-toolbar"

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <main>{children}</main>
        <Toolbar />
      </body>
    </html>
  )
}

Mounting the <Toolbar /> is required, as it’ll provide the necessary Server Action to execute revalidateTag when needed.

One final thing to keep in mind is that using other caching strategies (such as a time-based revalidate, or your own cache tags) will opt that query out of automatic on-demand revalidation, as we don’t want to interfere with your caching strategy.

Hassle Saved

The hassle of choosing the tagging strategy: if going with a fine-grained approach, { tags: [`post-${id}`] } or a more generic one, { tags: [‘cms-content‘] }, where a fine-grained is better but harder to set up. BaseHub’s strategy is naturally fine grained, because the tag is generated from the combination of query + variables, on a per-query basis.

The hassle of configuring webhooks: listening to the correct events, setting up the API handler, authenticating it with a new environment variable. With BaseHub, there’s no need to do any of this: just mount <Toolbar /> and you’re done.

The hassle of making sure you don’t forget to use the same caching strategy when developing new stuff. BaseHub automatically tags your queries so you don’t have to do so manually.

Automatic on-demand revalidation is now available. Give it a try with the latest version of basehub,

pnpm i basehub@latest