DocumentationNext.js IntegrationEnvironments & Caching

Environments & Caching

Understand the different environments and caching strategies you can leverage to improve your content editing experience.

Environments

Setting up your application so that it handles all of the environments in a seamless fashion is a very important part of integrating with BaseHub.

Local Environment

When developing in localhost, you’ll be writing new code and iterating over your content. This means adding new blocks, changing those blocks’ constraints, writing content and more—all at the same time.

In order to not break your flow, you’ll want two things:

  1. For the schema in BaseHub to be in sync with your IDE, and

  2. For the content to update as you write, without needing to commit it yet.

We bundled these two needs into one command:

basehub dev

This command generates the type-safe SDK and keeps it in sync with changes you make in basehub.com (this is called --watch mode); and also sets up the SDK so that it queries Draft content from your repository.

This is why we recommend you run it in parallel to next dev.

"scripts": {
  "dev": "basehub dev & next dev",
  "build": "basehub && next build",
  "start": "next start",
  "lint": "next lint"
},

Notice the single &.

Preview Environment

Setting up an easy way for editors to preview content before committing it into production is essential. We’ve designed our preview workflow with these three pillars in mind:

  1. Content should render in real time, as you write.

  2. Preview should be easy for developers to integrate.

  3. The integration should never degrade production performance in any way.

We achieve this is by using a couple of BaseHub components, <Pump /> and <Toolbar />, in combination to Next.js’ draftMode. Additionally, to bridge the gap between basehub.com (the dashboard) and your website, you’ll need to set up the “Preview” Button.

Name

Description

Author

<Pump />

Queries the API. Receives a draft prop that controls weather it’ll hit draft content and subscribe to real time changes, or just hit production.

BaseHub

draftMode

Allows you to detect Draft Mode inside a Server Component.

Next.js

<Toolbar />

Helper to turn on/off Draft Mode within your site, with zero-config.

BaseHub

Preview Button

Links from a BaseHub block into where that block is being rendered in your website.

BaseHub

This is how a simple code example can look like:

import { Toolbar } from 'basehub/next-toolbar'

export default function RootLayout({
  children,
}: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {/* Layout UI */}
        <main>{children}</main>
        <Toolbar />
      </body>
    </html>
  )
}
import { Pump } from "basehub/react-pump"
import { draftMode } from "next/headers"

const Page = () => {
  return (
    <Pump
      queries={[{ _sys: { id: true } }]}
      draft={draftMode().isEnabled} 
      next={{ revalidate: 30 }}
    >
      {async ([data]) => {
        "use server"

        return (
          <pre>
            <code>{JSON.stringify(data, null, 2)}</code>
          </pre>
        )
      }}
    </Pump>
  )
}

export default Page

Finally, we need to set up our Preview Buttons.

See how to set up Preview Buttons, and the whole preview environment really.

Production Environment

Once we’ve set up Local and Preview environments, most of the hard work is done. The only thing you need to make sure when going to production is for the SDK to be generated before the build step of your application.

"scripts": {
  "dev": "basehub dev & next dev",
  "build": "basehub && next build",
  "start": "next start",
  "lint": "next lint"
},

That should be it. You’re ready to deploy your website.

Caching

By default, Next.js will try to cache all of our requests made with fetch—and that includes BaseHub. While this makes subsequent requests to BaseHub much faster, it’ll essentially make your website’s content fully static, instead of reflecting the latest content changes from your BaseHub Repo.

import { Pump } from "basehub/react-pump"

const Page = async () => {
  return (
    <Pump queries={[{ __typename: true }]}>
      {async ([data]) => {
        "use server"
        // `data` will be cached by Next.js

        return <pre>{JSON.stringify(data, null, 2)}</pre>
      }}
    </Pump>
  )
}

export default Page

Time-Based Revalidation

The easiest way to revalidate content is to use Next.js’ time-based revalidate caching option, so that your data is cached and fast, but it also reacts to new content coming from BaseHub.

import { Pump } from "basehub/react-pump"

const Page = async () => {
  return (
    <Pump
      next={{ revalidate: 30 }} 
      queries={[{ __typename: true }]}
    >
      {async ([data]) => {
        "use server"
        // `data` will be stale after 30 seconds

        return <pre>{JSON.stringify(data, null, 2)}</pre>
      }}
    </Pump>
  )
}

export default Page

On-Demand Revalidation

You can leverage BaseHub Webhooks and Next.js On-Demand Revalidation to update the cache of your Next.js Apps in a more fine-grained fashion. Instead of checking to see if something changed every n seconds, you can listen to the repo.commit event from BaseHub and use revalidateTag or revalidatePath to revalidate on demand.

Set up API Endpoint that will revalidateTag1

app/api/revalidate-basehub/route.ts
import { revalidateTag } from "next/cache"

const webhookSecret = process.env.BASEHUB_WEBHOOK_SECRET
if (typeof webhookSecret !== "string") {
  throw new Error("Missing BASEHUB_WEBHOOK_SECRET.")
}

export const POST = (request: Request) => {
  /**
   * Authenticate the request.
   * For more security, follow the Svix guide on how to verify a webhook: https://docs.svix.com/receiving/verifying-payloads/how
   * For simplicity, and because revalidating a cache is not a security risk, we just do basic auth
   * with a Bearer token we'll set up ourselves.
   */
  const authorization = request.headers.get("authorization")
  if (authorization !== `Bearer ${webhookSecret}`) {
    return Response.json({ message: "Unauthorized." }, { status: 401 })
  }
  revalidateTag("basehub") 
  return Response.json({ revalidated: true, now: Date.now() })
}

The BASEHUB_WEBHOOK_SECRET can be whatever you define it to be. Just make sure you pass it via the Authorization header. You can generate a random password using the 1Password generator.

Add a Cache Tag to your queries2

import { Pump } from "basehub/react-pump"

const Page = async () => {
  return (
    <Pump
      next={{ tags: ["basehub"] }} 
      queries={[{ __typename: true }]}
    >
      {async ([data]) => {
        "use server"

        return <pre>{JSON.stringify(data, null, 2)}</pre>
      }}
    </Pump>
  )
}

export default Page

Create the Webhook3

To do this final step, you’ll go to your repo’s README and create a new Webhook Endpoint:

Contact us if you need a helping hand with this integration.