import { DEFAULT_META_DESCRIPTION, SITE_NAME } from './constants' const CANONICAL_ORIGIN = 'https://supabase.com' const ORG_ID = `${CANONICAL_ORIGIN}/#organization` const SITE_ID = `${CANONICAL_ORIGIN}/#website` const ORG_SAMEAS = [ 'https://github.com/supabase', 'https://twitter.com/supabase', 'https://www.linkedin.com/company/supabase', 'https://youtube.com/c/supabase', 'https://discord.supabase.com/', 'https://www.tiktok.com/@supabase.com', 'https://www.instagram.com/supabasecom', ] const ORG_LOGO_URL = `${CANONICAL_ORIGIN}/images/og/supabase-og.png` type JsonLdSchema = Record | Record[] export function serializeJsonLd(schema: JsonLdSchema): string { return JSON.stringify(schema) .replace(//g, '\\u003e') .replace(/&/g, '\\u0026') } interface OrganizationSchemaInput { description?: string } export function organizationSchema(input: OrganizationSchemaInput = {}) { return { '@context': 'https://schema.org', '@type': 'Organization', '@id': ORG_ID, name: SITE_NAME, url: CANONICAL_ORIGIN, logo: { '@type': 'ImageObject', url: ORG_LOGO_URL, }, description: input.description ?? DEFAULT_META_DESCRIPTION, legalName: 'Supabase Pte. Ltd.', contactPoint: { '@type': 'ContactPoint', contactType: 'customer support', url: `${CANONICAL_ORIGIN}/support`, }, address: { '@type': 'PostalAddress', streetAddress: '65 Chulia Street #38-02/03, OCBC Centre', addressLocality: 'Singapore', postalCode: '049513', addressCountry: 'SG', }, sameAs: ORG_SAMEAS, } } interface WebSiteSchemaInput { description?: string } export function websiteSchema(input: WebSiteSchemaInput = {}) { return { '@context': 'https://schema.org', '@type': 'WebSite', '@id': SITE_ID, url: CANONICAL_ORIGIN, name: SITE_NAME, description: input.description ?? DEFAULT_META_DESCRIPTION, publisher: { '@id': ORG_ID }, } } interface SoftwareApplicationSchemaInput { name: string description: string url: string image: string applicationCategory?: string } export function softwareApplicationSchema(input: SoftwareApplicationSchemaInput) { return { '@context': 'https://schema.org', '@type': 'SoftwareApplication', name: input.name, description: input.description, url: input.url, image: input.image, applicationCategory: input.applicationCategory ?? 'DeveloperApplication', operatingSystem: 'Cross-platform', publisher: { '@id': ORG_ID }, } } interface ServiceSchemaInput { name: string description: string url: string serviceType: string offerings: Array<{ name: string; url: string }> } export function serviceSchema(input: ServiceSchemaInput) { return { '@context': 'https://schema.org', '@type': 'Service', name: input.name, description: input.description, url: input.url, serviceType: input.serviceType, provider: { '@id': ORG_ID }, hasOfferCatalog: { '@type': 'OfferCatalog', name: `${input.name} products`, itemListElement: input.offerings.map((offering) => ({ '@type': 'Offer', itemOffered: { '@type': 'Service', name: offering.name, url: offering.url, }, })), }, } } export interface BreadcrumbItem { name: string url: string } export function breadcrumbListSchema(items: BreadcrumbItem[]) { return { '@context': 'https://schema.org', '@type': 'BreadcrumbList', itemListElement: items.map((item, index) => ({ '@type': 'ListItem', position: index + 1, name: item.name, item: item.url, })), } } interface BlogPostingSchemaInput { url: string headline: string description?: string image: string datePublished: string dateModified: string authors: Array<{ name: string; url?: string }> } export function blogPostingSchema(input: BlogPostingSchemaInput) { return { '@context': 'https://schema.org', '@type': 'BlogPosting', mainEntityOfPage: { '@type': 'WebPage', '@id': input.url, }, headline: input.headline, description: input.description, image: input.image, datePublished: input.datePublished, dateModified: input.dateModified, author: input.authors.map((a) => ({ '@type': 'Person', name: a.name, ...(a.url ? { url: a.url } : {}), })), publisher: { '@type': 'Organization', '@id': ORG_ID, name: SITE_NAME, logo: { '@type': 'ImageObject', url: ORG_LOGO_URL, }, }, } }