Skip to content

Heros and Modules

Our Sanity projects aim to be as flexible as they can be. This is why for most post types we like to use heros and modules, so the users have a series of building blocks available to be able to bring new pages to life while following a carefully curated design system.

To implement this in our backend we make use of our modules folder, which contains our common (all modules) and our heros folders.

These both contain an index that will determine which post type receives which heros and modules when calling the heros() and modules() islands.

To facilitate this process, we have a template section where each post type can be assigned both heros and modules.

modules/hero/index.js
import {defineField} from 'sanity'
import {main_hero} from './main_hero'
import {centered_hero} from './centered_hero'
import {solution_hero} from './solution_hero'
import {soft_grid_hero} from './soft_grid_hero'
import {hero_with_image} from './hero_with_image'
import {hero_with_optional_form_field} from './hero_with_optional_form_field'
import {hero_gradient_image} from './hero_gradient_image'
import {hero_with_form} from './hero_with_form'
import {hero_with_image_optional_form} from './hero_with_image_optional_form'
import {hero_platform} from './hero_platform'
export const heros = (template) => {
const availableHeros = getHeros(template)
let initialValue;
if(template && template !== 'landing_pages') {
initialValue = availableHeros.map((hero) => ({_type: hero.name}))
}
return [
defineField({
name: 'heros',
title: 'Heros',
type: 'array',
of: getHeros(template),
...(initialValue ? { initialValue } : {}),
validation: (Rule) => Rule.max(1),
}),
]
}
const getHeros = (template) => {
const allHeros = [...main_hero(), ...centered_hero(),...hero_with_image(), ...hero_with_optional_form_field(), ...hero_gradient_image(), ...hero_with_form() , ...hero_with_image_optional_form(),...soft_grid_hero(), ...hero_platform()]
const templates = {
landing_pages: [...allHeros],
solutions: [...solution_hero()],
products: [...soft_grid_hero()],
integrations: [...hero_gradient_image()],
environments: [
...hero_gradient_image(),
],
use_cases : [...soft_grid_hero()],
demos: [],
customer_stories: [],
partners: [...hero_with_image()],
blogs: [],
news: [],
freeware: [...hero_with_optional_form_field()],
guides: [],
research: [],
publications: [],
podcasts: [],
webinars: [],
webinar_series: [],
events: [],
compliance: [],
cybersecurity_frameworks: [],
attack_catalogue: [],
security_concepts: [],
architectural_concepts: [],
professional_services: [...centered_hero()],
legal : [...centered_hero()]
}
return template ? templates[template] : allHeros
}

So here we determine if we need all heros or only some ones. If a template is called, we use the heros available to that template. If not, we use all heros. So in this case, the pages post type:

schemaTypes/contentTypes/pages.js
fields: [
...title(),
...metaAttributes({showImage : false , showHeaderFooter : { header : true, footer: true} }),
...heros(),
...modules(),
],

Will receive all heros and modules, because it calls the islands but does not use any templates.

Instead, our solutions pages:

schemaTypes/contentTypes/solutions.js
fields: [
...title(),
...metaAttributes({section_slug: 'solutions', showAdditional: true, showImage : false}),
...heros('solutions'),
...modules('solutions'),
],

Use the solutions template, so they will receive the heros and modules available to them in that one.

The existence of heros and modules does not mean that every page needs them. For certain post types, such as the resources single pages, we do not use them, as we want them to have a unified design.

This means that in those type definitions we use regular islands instead of calling our heros and modules islands, like in blog for instance:

schemas/movies.ts
import {metaAttributes} from '@attributes/meta_attributes.js'
import {title} from '@island/title.js'
import {repeater} from '@island/repeater'
import {reference} from '@island/reference'
import {topic_reference} from '@island/topic_reference'
import {wysiwyg} from '@island/wysiwyg'
import {boolean} from '@island/boolean'
import {date} from '@island/date'
export default {
name: 'blogs',
title: 'Blogs',
type: 'document',
fields: [
...title(),
...metaAttributes({
section_slug: 'resources/blog',
showAdditional: true,
showTaxonomies: {
showGroup: true,
showCompliance: true,
showIndustry: true,
showProducts: true,
showSolutions: true,
showTechnology: true,
},
showImage: false
}),
...date({
localized: false,
options: {
dateFormat: 'MMM DD, Y',
calendarTodayLabel: 'Today',
},
title: 'Publication date',
dateOnly: true,
required :true
}),
...date({
localized: false,
options: {
dateFormat: 'MMM DD, Y',
calendarTodayLabel: 'Today',
},
name: 'update_date',
title: 'Update date',
dateOnly: true,
}),
...topic_reference(),
...wysiwyg({
title: 'Hero Description',
name: 'hero_description',
type : 'simple',
description:
'Description that will be shown on the top of the single pages after the hero title',
}),
...wysiwyg({
title: 'Article Content',
description:'Content of the blog post, use the WYSIWYG editor to format your text, add images, links, etc.',
}),
...boolean({
title: 'Should this blog have a list of themes instead of a table of contents?',
localized: false,
}),
...repeater({
title: 'Keywords',
localized: false,
fields: [
...title({
title: 'Keyword',
description: 'Enter a keyword for the blog post',
}),
],
hidden: ({parent}) => !parent.boolean,
preview: {
select: {
title: 'title.en',
},
prepare(selection) {
const {title} = selection
return {
title: title || 'No keyword',
}
},
},
}),
...reference({
title: 'Author',
type: ['people'],
name: 'author',
localized: false,
}),
],
preview: {
select: {
title: 'title.en',
subtitle : 'attributes.slug',
},
prepare(selection) {
return {
title: selection.title,
subtitle: selection.subtitle,
}
},
},
}

The process to add a hero or module is simple but it has several steps that need to be followed.

In Sanity:

  • Create the module .js file with its fields and properties
  • Add the module / hero in its corresponding index.js

In Astro:

  • Create the query file for that module
  • Create the Astro component file for that module
  • Add both into the querying index

Watch this video to see a full rundown of how we create a module, from Sanity to Astro