Next.js

Connecting BaseHub to Next.js is very simple. First of all, you’ll need to have the basehub SDK installed and configured. Refer to our guide to do so.

Once installed, this is how you’d query BaseHub from Next.js

app/page.tsx

import { basehub } from "basehub"

const Page = async () => {
  const data = await basehub({ next: { revalidate: 30 }}).query({
    __typename: true
  })

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

export default Page

Let’s explore some common use cases you might encounter with Next.js.

Creating a Blog

Imagine you're creating a Blog. This would be a typical file structure:

app/
├── blog/
│   ├── page.tsx
│   └── [slug]/
│       └── page.tsx
next.config.js
package.json
... more files

This is how our app/blog/page.tsx could look like:

app/blog/page.tsx

import { basehub } from 'basehub'
import { RichText } from 'basehub/react'
import Link from 'next/link'
import type { Metadata } from 'next'

export const revalidate = 60
export const dynamic = 'force-static'

export async function generateMetadata(): Promise<Metadata> {
  const { blog } = await basehub().query({
    blog: { meta: { title: true, description: true, ogImage: { url: true } } },
  })

  return {
    title: blog.meta.title,
    description: blog.meta.description,
    // etc...
  }
}

const BlogPage = async () => {
  const { blog } = await basehub({ next: { revalidate: 60 } }).query({
    blog: {
      header: { title: true, subtitle: { json: { content: true } } },
      posts: {
        __args: { first: 10, orderBy: 'publishDate__DESC' },
        items: {
          _id: true,
          _title: true,
          _slug: true,
          subtitle: true,
          publishDate: true,
          // more things
        },
      },
    },
  })

  return (
    <div>
      <h1>{blog.header.title}</h1>
      <div>
        <RichText>{blog.header.subtitle.json.content}</RichText>
      </div>
      <ul>
        {blog.posts.items.map((post) => {
          return (
            <li key={post._id}>
              <Link href={`/blog/${post._slug}`}>{post._title}</Link>
            </li>
          )
        })}
      </ul>
    </div>
  )
}

export default BlogPage

And then, our app/blog/[slug]/page.tsx could be something like the following:

app/blog/[slug]/page.tsx

import { basehub } from 'basehub'
import { notFound } from 'next/navigation'
import { RichText } from 'basehub/react'
import type { Metadata } from 'next'

export async function generateStaticParams() {
  const {
    blog: { posts },
  } = await basehub({ cache: 'no-store' }).query({
    blog: { posts: { items: { _slug: true } } },
  })

  return posts.items.map((post) => ({ slug: post._slug }))
}

export async function generateMetadata({
  params,
}: {
  params: { slug: string }
}): Promise<Metadata> {
  const post = await getPostBySlug(params.slug)

  return {
    title: post.meta.title,
    description: post.meta.description,
    // etc...
  }
}

const BlogPage = async ({ params }: { params: { slug: string } }) => {
  const post = await getPostBySlug(params.slug)

  return (
    <div>
      <h1>{post._title}</h1>
      <p>Published at: {new Date(post.publishDate).toLocaleString()}</p>
      <RichText>{post.content.json.content}</RichText>
    </div>
  )
}

async function getPostBySlug(slug: string) {
  const { blog } = await basehub({ next: { revalidate: 60 } }).query({
    blog: {
      posts: {
        __args: { first: 1, filter: { _sys_slug: { eq: slug } } },
        items: {
          _id: true,
          _title: true,
          publishDate: true,
          content: { json: { content: true } },
          meta: { title: true, description: true },
          coverImage: { url: true },
        },
      },
    },
  })

  const [post] = blog.posts.items
  if (!post) notFound()

  return post
}

export default BlogPage

We’re hoping to release more full, real-world examples soon. Join the Community Discord to ask our community for specific use cases!

Caching, and Revalidation

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.

app/page.tsx

import { basehub } from "basehub"

const Page = async () => {
  const data = await basehub().query({
    __typename: true
  })
  // `data` will be cached

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

export default Page

Our recommendation is to use their time-based revalidate caching option, so that your data is cached and fast, but it also reacts to new content coming from BaseHub.

app/page.tsx

import { basehub } from "basehub"

const Page = async () => {
  const data = await basehub().query({ 
  const data = await basehub({ next: { revalidate: 30 } }).query({ 
    __typename: true
  })
  // `data` will get revalidated 30 seconds after the previous query

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

export default Page

You can pass any fetch options that Next.js exposes, such as cache, and next to configure caching for your use case.

Dedupe Requests

By using the cache function React exposes, we can dedupe API Requests so that we reuse work done previously. This is great, cause it means we can create helper functions, such as getPostBySlug, cache it with React’s cache API, and save us from doing multiple API requests to BaseHub to get the same data. For example:

app/basehub/queries.ts

import { basehub } from 'basehub'
import { notFound } from 'next/navigation'
import { cache } from 'react'

export const getPostBySlug = cache(async (slug: string) => {
  const { blog } = await basehub({ next: { revalidate: 60 } }).query({
    blog: {
      posts: {
        __args: { first: 1, filter: { _sys_slug: { eq: slug } } },
        items: {
          _id: true,
          _title: true,
          publishDate: true,
          content: { json: { content: true } },
          meta: { title: true, description: true },
          coverImage: { url: true },
        },
      },
    },
  })

  const [post] = blog.posts.items
  if (!post) notFound()

  return post
})

Draft Mode

To set up a preview workflow with BaseHub and Next.js, you’ll need to first configure Draft Mode in Next.js. If you use Vercel, the Vercel Toolbar will come with Draft Mode already configured, which is great.

Next, you’ll need to update your queries to get the Draft content if draftMode().isEnabled. Fortunately, the basehub SDK supports a draft parameter that will make the query get draft content. For example, in the context of our previous getPostBySlug helper function:

app/basehub/queries.ts

import { basehub } from 'basehub'
import { draftMode } from 'next/headers'
import { notFound } from 'next/navigation'
import { cache } from 'react'

export const getPostBySlug = cache(async (slug: string) => {
  const { isEnabled: isDraftMode } = draftMode()

  const { blog } = await basehub({
    next: { revalidate: 60 },
    draft: isDraftMode,
  }).query({
    blog: {
      posts: {
        __args: { first: 1, filter: { _sys_slug: { eq: slug } } },
        items: {
          _id: true,
          _title: true,
          publishDate: true,
          content: { json: { content: true } },
          meta: { title: true, description: true },
          coverImage: { url: true },
        },
      },
    },
  })

  const [post] = blog.posts.items
  if (!post) notFound()

  return post
})

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.

Let’s set it up!

1. Set up API Endpoint that will revalidateTag

/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. Get it from the [Webhook Portal](https://basehub.com/docs/connecting-to-your-app/webhooks).'
  )
}

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.

2. Make sure your query client uses the Cache Tag you want to revalidate

lib/basehub-client.ts

import { basehub } from "basehub"

export const basehubClient = basehub({ next: { tags: ['basehub'] } })

3. Create the Webhook

To do this final step, follow our guide on setting up Webhooks via the Webhook Portal.

Troubleshooting

“Error: type 'Query' does not have a field 'blogIndex' “

This error may pop up when using basehub sometimes, and it could be due to two reasons:

  1. You altered your schema in BaseHub and didn’t run the generator again. Make sure to run basehub every time you alter your schema, so that we can re-infer it.

  2. You already ran the generator, but Next.js cached the previous generated code in the .next directory (this is a directory Next.js uses to store compiled code while running the dev server). To fix this, just delete the .next directory and start your project again.

TypeScript not autocompleting after re-generating the schema

This gets fixed by restarting the TS server. In VSCode, you can do:

  • Mac: Cmd + Shift + P + “Restart TS Server“

  • Win: Ctrl + Shift + P + “Restart TS Server“