Skip to content

Instantly share code, notes, and snippets.

@employee451
Last active May 7, 2024 10:17
Show Gist options
  • Save employee451/2f05d49b5d8251e2acb25fe408426b83 to your computer and use it in GitHub Desktop.
Save employee451/2f05d49b5d8251e2acb25fe408426b83 to your computer and use it in GitHub Desktop.
Resolve references in Sanity data (GraphQL)
import { groq } from 'next-sanity' // Replace with another library if you don't use Next.JS
import { client } from './client' // Replace this with wherever you have set up your client
/**
* Resolves all unresolved references when fetching from the Sanity GraphQL API.
* There is no native way to do this, so we have to do it manually.
* @param data
*/
export const resolveSanityGraphqlReferences = async <T = unknown>(
data: T
): Promise<T> => {
// If we are not dealing with an object, simply return the original value
if (typeof data !== 'object' || !data) {
return data
}
return Object.entries(data).reduce<any>(async (acc, [key, value]) => {
// If it's a reference, we need to resolve it
if (value?._type && value._type === 'reference') {
const resolvedReference = await client.fetch(
groq`*[_id == $ref][0]`,
{
ref: value._ref,
}
)
return {
...(await acc),
[key]: resolvedReference,
}
}
// If it's a nested object (not a reference), we continue the crawling
if (value?._type) {
return {
...(await acc),
[key]: await resolveSanityGraphqlReferences(value),
}
}
// For arrays, crawl every item in the array
if (Array.isArray(value)) {
return {
...(await acc),
[key]: await Promise.all(value.map(resolveSanityGraphqlReferences)),
}
}
// If it's any other value (primitive), keep the value as it is
return {
...(await acc),
[key]: value,
}
}, {})
}
@employee451
Copy link
Author

employee451 commented Nov 29, 2022

Tired of the Sanity GraphQL API not resolving references in your raw portable text (with no option to resolve the references)?

In my case this meant that internal links were a reference, and I couldn't access the slug of the post which was being linked to. Here is a solution which you can use inside a library like Next.JS with something like getStaticProps (or on the client, although this will make your site slower).

When you have fetched the data for a document, you can pass it through this function, and it will resolve the unresolved references for you. Since it will run on build-time (if you use getStaticProps in Next.JS), it will not make your site any slower. The extra time is added to the build of your site

@doublejosh
Copy link

I'm looking to solve the same problem but in the SSR realm on GatsbyJS.

References are also truncated during the build, I've seen a few client-layer solutions... but these values are necessary during the static page generation.

@doublejosh
Copy link

OMG, found the issue! You can get PortableText references to populate more data by boosting the resolveReferences depth higher than you might expect.

sanity-io/gatsby-source-sanity#37 (comment)

@logemann
Copy link

I think you could use something like this, dont you?

_rawTestimonials(resolveReferences: { maxDepth: 5 })

where "testimonials" is an array with references in my schema and those references hold images, which are also references. So its a few levels deep. You can use the _raw* accessor of the field and tell sanity how much to traverse into.

@Silon
Copy link

Silon commented May 7, 2024

I created optimised version with single groq query (we had >100k requests per day because of this). It's not so beautiful but it works.

export const getResolveSanityGraphqlReferences = async <T = unknown>(data: T): Promise<T> => {
  if (typeof data !== 'object' || !data) {
    return data;
  }

  const references = {};

  const findReferences = (value, key: string) => {
    if (value?._type === 'reference') {
      references[key] = value._ref;
    }

    if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
      Object.keys(value).forEach((nestedKey) => findReferences(value[nestedKey], `${key}.${nestedKey}`));
    }

    if (Array.isArray(value)) {
      value.forEach((item, index) => findReferences(item, `${key}[${index}]`));
    }
  };

  Object.keys(data).forEach((key) => findReferences(data[key], key));

  if (Object.keys(references).length === 0) {
    return data;
  }

  const resolvedReferences = await client.fetch(
    groq`*[_id in $refs]{
      _id,
      link,
    }`,
    {
      refs: Object.values(references),
    },
  );

  const resolveReferences = (value: any, key: string) => {
    if (references[key]) {
      return resolvedReferences.find((ref) => ref._id === references[key]);
    }

    if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
      return Object.keys(value).reduce<any>(
        (acc, nestedKey) => ({
          ...acc,
          [nestedKey]: resolveReferences(value[nestedKey], `${key}.${nestedKey}`),
        }),
        {},
      );
    }

    if (Array.isArray(value)) {
      return value.map((item, index) => resolveReferences(item, `${key}[${index}]`));
    }

    return value;
  };

  return Object.keys(data).reduce<any>(
    (acc, key) => ({
      ...acc,
      [key]: resolveReferences(data[key], key),
    }),
    {},
  );
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment