Skip to main content

Personalizing on Sanity Data

Optimize Deprecation

Uniform Optimize has been deprecated and replaced by Context, a more powerful and flexible personalization solution. Optimize is not being discontinued at this time, but it will not receive updates or new features.

We do not recommend starting a new project with Optimize. If you have an existing project that uses Optimize, you can upgrade your project to Context at no cost using our upgrade guide. If you have any issues with this process you can contact our team.

Once you have installed the Uniform Optimize plugin for Sanity, the next step is to get the Sanity content into your application and start personalizing it.

Fetch the Data From Sanity

Let us assume we have a Sanity JavaScript SDK query that retrieves a single page entry by matching on the slug field.

// https://www.sanity.io/docs/js-client
import createSanityClient from '@sanity/client';
// see the code sample at the bottom of this document for `optimizeHelpers` module code.
import { parseIntentTagsField } from './optimizeHelpers';

const client = createSanityClient({
projectId: 'your-project-id',
dataset: 'bikeshop',
apiVersion: '2019-01-29', // use current UTC date - see "specifying API version"!
token: 'sanity-auth-token', // or leave blank for unauthenticated usage
useCdn: true, // `false` if you want to ensure fresh data
});

export const getPageBySlug = async (slug: string): Promise<PageDocument> => {
// The page query can contain arbitrary `reference` types to components, so we
// need to use GROQ reference projection combined with object expansion `...` to
// "expand" all fields for each component.
//
// While expansion is straightfoward for top-level, non-reference fields, "personalizable"
// content type will likely contain a field that is an array of references to
// personalized "variations" of content entries.
//
// Sanity will not expand the nested references by default when querying, so we have to tell
// Sanity to expand the "variations" references and include all fields for each
// referenced (variation) object.
//
// Furthermore, each variation object may contain an "image" field that is also not expanded
// by default, so we need to add expansion for that nested field as well.
// The same is true for other content types that contain a top-level "image" field.
//
// NOTE: this is just an example query of how to fetch personalizable data along with
// expanded content/data, your final query may be different.
const pageQuery = `*[_type == "page" && slug.current == $slug][0] {
_id,
title,
components[]->{
...,
variations[]->{
...,
image {
...,
asset->
}
},
image {
...,
asset->
}
}
}`;

const result = await client.fetch(pageQuery, { slug });
// Parse intent tag fields to JSON instead of requiring developers to parse the fields
// separately in their components.
result.components = result.components.map(parseIntentTagsField);
return result;
};

For the code example above, we assume the page content type has a components field, a reference field to other content entries created in Sanity. One of these components also contains a reference field, named variations, that contains references to content variations.

For example, one of the components in the components field is named Personalized Hero. It contains a field named variations (friendly name Hero Options), a reference field containing references to all possible Hero content entries that are eligible for personalization.

The page query defined in the code sample above uses a Sanity GROQ query that uses reference project and object expansion to for both the components field and variations field so that Sanity will deliver "expanded" content for those reference fields.

Use Sanity Data With the Personalize Component

The variations field contains references to personalizable content entries. Some helper functions have been provided as a code sample to read the Sanity response entries and convert them into the format expected by the Personalize component (see below). The result can be passed to the variations field.

import { Personalize } from '@uniformdev/optimize-tracker-react';
import { Hero } from './Hero';
import { mapSanityDocumentToPersonalizableItem } from './optimize-helpers';

export const PersonalizedHero: React.FC = ({ variations }) => {
const finalVariations: PersonalizableListItem[] = variations.map(mapSanityDocumentToPersonalizableItem);

return (
<Personalize
name="Personalized Hero"
variations={finalVariations}
trackingEventName="heroPersonalized"
component={Hero}
loadingMode={PersonalizedHeroLoading}
/>
);
};
tip

In the example we used React, but the SanityOptimizeListReader function is framework-agnostic.

Helper Functions for Parsing and Using Intent Tags Fields

import { IntentTags } from '@uniformdev/optimize-common';
import { PersonalizableListItem } from '@uniformdev/optimize-tracker-common';

/**
* This function is used for mapping certain properties found on a Sanity document to "known"
* Optimize properties that can be used by the Optimize tracker and behavior tracking functionality.
*/
export function mapSanityDocumentToPersonalizableItem(document): PersonalizableListItem {
// parse the intent tags field (if it hasn't already been parsed)
const parsedDocument = parseIntentTagsField(document);
return {
...document,
type: document._type,
intentTag: parsedDocument.unfrmOptIntentTag,
};
}

/**
* This function mutates the provided document, parsing the `unfrmOptIntentTag` field to JSON if the field exists on the document.
*/
export function parseIntentTagsField<T>(document: T): T {
if (
document &&
document.unfrmOptIntentTag &&
typeof document.unfrmOptIntentTag.uniformIntentTags === 'string'
) {
// note: due to Sanity schema structure, the field name is `unfrmOptIntentTag` but the _actual_ field value that we're interested
// in is nested within `unfrmOptIntentTag.uniformIntentTags`.
(document as any).unfrmOptIntentTag = JSON.parse(
(document.unfrmOptIntentTag?.uniformIntentTags as any) || '{}'
) as IntentTags;
}
return document;
}