Error integrating Algolia with NextJS and Sanity

6 replies
Last updated: Jan 24, 2022
Hello, I am trying to integrate algolia on one my NextJS project. I was trying to sync with
sanity-algolia
via Next API but I keep getting error
error - TypeError: Cannot read property 'created' of undefined
    at _callee$ (/Volumes/workspace/clients/radiology-associates/ra-frontend/node_modules/sanity-algolia/dist/sanity-algolia.cjs.development.js:891:57)
    at tryCatch (/Volumes/workspace/clients/radiology-associates/ra-frontend/node_modules/sanity-algolia/dist/sanity-algolia.cjs.development.js:129:40)
    at Generator.invoke [as _invoke] (/Volumes/workspace/clients/radiology-associates/ra-frontend/node_modules/sanity-algolia/dist/sanity-algolia.cjs.development.js:359:22)
    at Generator.next (/Volumes/workspace/clients/radiology-associates/ra-frontend/node_modules/sanity-algolia/dist/sanity-algolia.cjs.development.js:184:21)
    at asyncGeneratorStep (/Volumes/workspace/clients/radiology-associates/ra-frontend/node_modules/sanity-algolia/dist/sanity-algolia.cjs.development.js:29:24)
    at _next (/Volumes/workspace/clients/radiology-associates/ra-frontend/node_modules/sanity-algolia/dist/sanity-algolia.cjs.development.js:51:9) {
  page: '/api/sanityAlgolia'
}
AI Update

I can see from the error you're encountering that you're using the sanity-algolia package, which is actually no longer the recommended approach for integrating Algolia with Sanity. The package has been deprecated because both Sanity webhooks and the Algolia client have improved significantly since it was created, making the package unnecessary.

The error Cannot read property 'created' of undefined is occurring because the deprecated package expects a specific payload structure that doesn't match modern webhook implementations. The GitHub repository explicitly states: "Both Sanity webhooks and the Algolia client have significantly improved since the sanity-algolia package was originally released, so much so that the package is no longer necessary."

Instead of using the sanity-algolia package, you should integrate Algolia directly using either:

Sanity Functions are the modern, serverless compute solution built into Sanity. They're perfect for this use case because they run on Sanity's infrastructure and can react to document changes automatically:

// sanity.blueprint.ts
import { defineBlueprint } from 'sanity'
import algoliasearch from 'algoliasearch'

const client = algoliasearch(
  process.env.ALGOLIA_APP_ID!,
  process.env.ALGOLIA_ADMIN_KEY!
)
const index = client.initIndex('your_index_name')

export default defineBlueprint({
  functions: {
    algoliaSync: {
      on: 'document.create,document.update,document.delete',
      filter: '_type in ["post", "page"]', // Specify your document types
      handler: async ({ event, sanityClient }) => {
        const { _id, operation } = event

        if (operation === 'delete') {
          await index.deleteObject(_id)
          return { success: true, action: 'deleted' }
        }

        // Fetch the full document with the fields you need
        const document = await sanityClient.fetch(
          `*[_id == $id][0]{
            objectID: _id,
            title,
            slug,
            content,
            // Add other fields you want to index
          }`,
          { id: _id }
        )

        if (document) {
          await index.saveObject(document)
          return { success: true, action: 'indexed' }
        }

        return { success: false, reason: 'document not found' }
      }
    }
  }
})

2. GROQ-Powered Webhooks with Next.js API Route

If you prefer to keep using your Next.js API route approach, modernize it by using GROQ-powered webhooks and the Algolia JavaScript client directly:

// pages/api/algolia-sync.js (or app/api/algolia-sync/route.ts for App Router)
import algoliasearch from 'algoliasearch'
import { createClient } from '@sanity/client'
import { isValidSignature, SIGNATURE_HEADER_NAME } from '@sanity/webhook'

const algolia = algoliasearch(
  process.env.ALGOLIA_APP_ID,
  process.env.ALGOLIA_ADMIN_KEY
)
const index = algolia.initIndex('your_index_name')

const sanityClient = createClient({
  projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID,
  dataset: process.env.NEXT_PUBLIC_SANITY_DATASET,
  apiVersion: '2024-01-01',
  useCdn: false
})

export default async function handler(req, res) {
  if (req.method !== 'POST') {
    return res.status(405).json({ message: 'Method not allowed' })
  }

  // Verify webhook signature
  const signature = req.headers[SIGNATURE_HEADER_NAME]
  const isValid = isValidSignature(
    JSON.stringify(req.body),
    signature,
    process.env.SANITY_WEBHOOK_SECRET
  )

  if (!isValid) {
    return res.status(401).json({ message: 'Invalid signature' })
  }

  try {
    const { _id, operation, value } = req.body

    // Handle delete operation
    if (operation === 'delete') {
      await index.deleteObject(_id)
      return res.status(200).json({ success: true, action: 'deleted' })
    }

    // For create/update, the value from the webhook projection contains the data
    if (value) {
      await index.saveObject(value)
      return res.status(200).json({ success: true, action: 'indexed' })
    }

    return res.status(400).json({ error: 'No value provided' })
  } catch (error) {
    console.error('Algolia sync error:', error)
    return res.status(500).json({ error: error.message })
  }
}

Setting Up the GROQ-Powered Webhook

In your Sanity webhook settings, configure:

Filter:

_type == 'post'

Projection:

{
  "transactionId": _rev,
  "projectId": sanity::projectId(),
  "dataset": sanity::dataset(),
  _id,
  "operation": delta::operation(),
  "value": {
    "objectID": _id,
    "title": title,
    "slug": slug.current,
    "body": pt::text(content),
    "_type": _type,
    "coverImage": coverImage.asset->url,
    "date": date,
    "_createdAt": _createdAt,
    "_updatedAt": _updatedAt
  }
}

Migration Steps

  1. Remove the deprecated package: npm uninstall sanity-algolia
  2. Install the Algolia client: npm install algoliasearch @sanity/webhook
  3. Implement one of the approaches above
  4. Update your webhook configuration in Sanity to use the GROQ projection
  5. Test with a document update to verify it indexes correctly

Why This Approach is Better

  1. No deprecated dependencies: You're using the official, maintained Algolia JavaScript client
  2. More control: You define exactly what data gets indexed using GROQ
  3. Clearer error handling: Direct integration makes debugging easier
  4. Modern architecture: Especially with Sanity Functions, you get automatic scaling and no external hosting needed

The sanity-algolia package served its purpose when it was created, but the ecosystem has evolved. The direct integration approach gives you more flexibility and uses current best practices for both Sanity and Algolia. You can find a complete implementation guide in the official Sanity + Algolia guide and reference implementation in the sanity-algolia repository.

Here is my current file looks like

import sanityClient from "@sanity/client";
import algoliasearch from "algoliasearch";
import { NextApiRequest, NextApiResponse } from "next";
import indexer from "sanity-algolia";

const algolia = algoliasearch(
  process.env.NEXT_PUBLIC_ALGOLIA_APPID,
  process.env.ALGOLIA_ADMINKEY
);
const sanity = sanityClient({
  projectId: process.env.NEXT_PUBLIC_PROJECTID,
  dataset: process.env.NEXT_PUBLIC_DATASET,
  apiVersion: "v2021-03-25",
  token: process.env.SANITY_TOKEN,
  useCdn: false,
});

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.headers["content-type"] !== "application/json") {
    res.status(400);
    res.json({ message: "Bad request" });
    return;
  }
  try {
    const sanityAlgolia = indexer(
      // The first parameter maps a Sanity document type to its respective Algolia
      // search index. In this example both `post` and `article` Sanity types live
      // in the same Algolia index. Optionally you can also customize how the
      // document is fetched from Sanity by specifying a GROQ projection.
      {
        news: {
          index: algolia.initIndex("news"),
          projection: `{
            title,
            "slug": slug.current,
            "body": pt::text(body)
          }`,
        },
        page: {
          index: algolia.initIndex("pages"),
          projection: `{
            title,
            "slug": slug.current,
            "body": pt::text(body)
          }`,
        },
        service: {
          index: algolia.initIndex("services"),
          projection: `{
            name,
            "slug": slug.current,
            "text": section1.short_description
          }`,
        },
        physician: {
          index: algolia.initIndex("physicians"),
        },
        staff: {
          index: algolia.initIndex("staff"),
        },
        location: {
          index: algolia.initIndex("locations"),
        },
        patientForm: {
          index: algolia.initIndex("patientForms"),
        },
      },
      // The second parameter is a function that maps from a fetched Sanity document
      // to an Algolia Record. Here you can do further mutations to the data before
      // it is sent to Algolia.
      (document) => {
        return document;
      }
    );
    console.log(req.body);
    return sanityAlgolia
      .webhookSync(sanity, req.body)
      .then(() => res.status(200).send("ok"));
  } catch (error) {
    return error;
  }
}
There are couple things I would like to note that. I have tried console.log the
req.body
but it doesn’t give the format as
sanityAlgolia.webhookSync
wants.
webhookSync
want to return
ids: {
        created: string[];
        updated: string[];
        deleted: string[];
    };
But I am just sending
req.body
as github stated
Hey Shoaib! I think you're running into an issue with that Algolia package relying on Sanity's legacy webhooks. About a month ago, we switched to the new GROQ Powered Webhooks. You can't re-enable the legacy behavior, but you can mimic the old behavior using the method in the changelog .
Thank you
user M
, I am wondering if there is a way we know how to detect whether the webhook is for (created or updated or deleted)
Thank you
user M
, I am wondering if there is a way we know how to detect whether the webhook is for (created or updated or deleted)
user K
Were you able to resolve the above issue? 😊

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?