Skip to content

Commit

Permalink
feat: create sanity cms metadata utility function
Browse files Browse the repository at this point in the history
  • Loading branch information
Aerilym committed Oct 13, 2024
1 parent 11b5a76 commit f9969b2
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 4 deletions.
5 changes: 5 additions & 0 deletions apps/foundation/app/(Sanity)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { ReactNode } from 'react';
import '@session/ui/styles';
import type { Metadata } from 'next';

export const metadata: Metadata = {
title: 'STF | Sanity Studio',
};

export default function SanityLayout({ children }: { children: ReactNode }) {
return (
Expand Down
35 changes: 35 additions & 0 deletions apps/foundation/app/(Site)/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { getLandingPageSlug } from '@/lib/sanity/sanity-server';
import PortableText from '@/components/PortableText';
import logger from '@/lib/logger';
import { NEXTJS_EXPLICIT_IGNORED_ROUTES, NEXTJS_IGNORED_PATTERNS } from '@/lib/constants';
import type { Metadata, ResolvingMetadata } from 'next';
import { generateSanityMetadata } from '@session/sanity-cms/lib/metadata';

/**
* Force static rendering and cache the data of a layout or page by forcing `cookies()`, `headers()`
Expand All @@ -19,6 +21,39 @@ export const dynamic = 'force-static';
*/
export const dynamicParams = true;

export async function generateMetadata(
{ params }: PageProps,
parent: ResolvingMetadata
): Promise<Metadata> {
const slug = params.slug;
if (!slug) {
logger.warn(`No slug provided for metadata generation`);
return {};
}

if (
NEXTJS_EXPLICIT_IGNORED_ROUTES.includes(slug) ||
NEXTJS_IGNORED_PATTERNS.some((pattern) => slug.includes(pattern))
) {
return {};
}

logger.info(`Generating metadata for slug ${slug}`);

const page = await getPageBySlug({ client, slug });

if (!page) {
logger.warn(`No page found for slug ${slug}`);
return {};
}
const parentMetadata = await parent;
return generateSanityMetadata(client, {
seo: page.seo,
parentMetadata,
type: 'website',
});
}

export async function generateStaticParams() {
const pages = await getPagesSlugs({ client });
const slugs = new Set(pages.map((page) => page.slug.current));
Expand Down
36 changes: 36 additions & 0 deletions apps/foundation/app/(Site)/blog/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { getPostsSlugs } from '@session/sanity-cms/queries/getPosts';
import { getPostBySlug } from '@session/sanity-cms/queries/getPost';
import logger from '@/lib/logger';
import BlogPost from '@/app/(Site)/blog/[slug]/BlogPost';
import type { Metadata, ResolvingMetadata } from 'next';
import { generateSanityMetadata } from '@session/sanity-cms/lib/metadata';

/**
* Force static rendering and cache the data of a layout or page by forcing `cookies()`, `headers()`
Expand All @@ -17,6 +19,40 @@ export const dynamic = 'force-static';
*/
export const dynamicParams = true;

export async function generateMetadata(
{ params }: PageProps,
parent: ResolvingMetadata
): Promise<Metadata> {
const slug = params.slug;
if (!slug) {
logger.warn(`No slug provided for metadata generation`);
return {};
}

logger.info(`Generating metadata for slug ${slug}`);

const post = await getPostBySlug({ client, slug });

if (!post) {
logger.warn(`No post found for slug ${slug}`);
return {};
}
const parentMetadata = await parent;

const publishedTime = post.date ? new Date(post.date).toISOString() : undefined;
const authors = post.author?.name ? [post.author.name] : undefined;
const tags = post.tags?.length ? post.tags : undefined;

return generateSanityMetadata(client, {
seo: post.seo,
parentMetadata,
type: 'article',
publishedTime,
authors,
tags,
});
}

export async function generateStaticParams() {
const posts = await getPostsSlugs({ client });
const slugs = new Set(posts.map((post) => post.slug.current));
Expand Down
15 changes: 15 additions & 0 deletions apps/foundation/app/(Site)/blog/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import type { ReactNode } from 'react';
import { Footer } from '@/components/Footer';
import { getInitialSiteDataForSSR } from '@/lib/sanity/sanity-server';
import type { Metadata, ResolvingMetadata } from 'next';
import { generateSanityMetadata } from '@session/sanity-cms/lib/metadata';
import { client } from '@/lib/sanity/sanity.client';

export async function generateMetadata(parent: ResolvingMetadata): Promise<Metadata> {
const { settings } = await getInitialSiteDataForSSR();

return settings.blogSeo
? await generateSanityMetadata(client, {
seo: settings.blogSeo,
parentMetadata: await parent,
type: 'website',
})
: {};
}

export default async function BlogLayout({ children }: { children: ReactNode }) {
const { settings } = await getInitialSiteDataForSSR();
Expand Down
20 changes: 19 additions & 1 deletion apps/foundation/app/(Site)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@ import DevSheetServerSide from '@/components/DevSheetServerSide';
import { getInitialSiteDataForSSR } from '@/lib/sanity/sanity-server';
import Head from 'next/head';
import { isProduction } from '@session/util-js/env';
import { client } from '@/lib/sanity/sanity.client';
import { generateSanityMetadata } from '@session/sanity-cms/lib/metadata';
import type { Metadata } from 'next';

export async function generateMetadata(): Promise<Metadata> {
const { settings } = await getInitialSiteDataForSSR();

const generatedMetadata = settings.seo
? await generateSanityMetadata(client, {
seo: settings.seo,
type: 'website',
})
: {};

return {
...generatedMetadata,
manifest: '/site.webmanifest',
};
}

export default async function RootLayout({ children }: { children: ReactNode }) {
const { locale, direction, messages } = await getLocalizationData();
Expand All @@ -25,7 +44,6 @@ export default async function RootLayout({ children }: { children: ReactNode })
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="manifest" href="/site.webmanifest" />
</Head>
<GlobalProvider messages={messages} locale={locale}>
<body className="bg-session-white font-roboto-flex text-session-text-black mx-4 flex flex-col items-center overflow-x-hidden">
Expand Down
13 changes: 12 additions & 1 deletion apps/foundation/app/(Site)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getLandingPageSlug } from '@/lib/sanity/sanity-server';
import UniversalPage from './[slug]/page';
import UniversalPage, { generateMetadata as generateMetadataUniversalPage } from './[slug]/page';
import UniversalPageLayout from '@/app/(Site)/[slug]/layout';
import type { ResolvingMetadata } from 'next';

/**
* Force static rendering and cache the data of a layout or page by forcing `cookies()`, `headers()`
Expand All @@ -9,6 +10,16 @@ import UniversalPageLayout from '@/app/(Site)/[slug]/layout';
*/
export const dynamic = 'force-static';

export async function generateMetadata(parent: ResolvingMetadata) {
const slug = await getLandingPageSlug();

if (!slug) {
throw new Error('No landing page set in settings to generate metadata');
}

return generateMetadataUniversalPage({ params: { slug } }, parent);
}

export default async function LandingPage() {
const slug = await getLandingPageSlug();

Expand Down
11 changes: 10 additions & 1 deletion packages/sanity-cms/components/SanityImage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,25 @@ import type {
import { cn } from '@session/ui/lib/utils';
import { safeTry } from '@session/util-js/try';
import { Fragment } from 'react';
import type { SanityImageSource } from '@sanity/asset-utils';
import type { CustomImageType } from '../schemas/fields/basic/seo';

export type SanityImageType = ImageFieldsSchemaType | ImageFieldsSchemaTypeWithoutAltText;

export const getSanityImageUrlBuilder = (
client: SessionSanityClient,
value: SanityImageType | SanityImageSource | CustomImageType
) => {
return urlBuilder(client).image(value).fit('max').auto('format');
};

/**
* Build image data from Sanity image schema and the image source.
* @param client Sanity client
* @param value Sanity image schema
*/
async function buildImage(client: SessionSanityClient, value: SanityImageType) {
const src = urlBuilder(client).image(value).fit('max').auto('format').url();
const src = getSanityImageUrlBuilder(client, value).url();
const buffer = await fetch(src).then(async (res) => Buffer.from(await res.arrayBuffer()));

const {
Expand Down
83 changes: 83 additions & 0 deletions packages/sanity-cms/lib/metadata.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { getSanityImageUrlBuilder } from '../components/SanityImage';
import type { SeoType } from '../schemas/fields/basic/seo';
import type { Metadata, ResolvedMetadata } from 'next';
import type { SessionSanityClient } from './client';

type GenericMetadataProps = {
seo?: SeoType;
parentMetadata?: ResolvedMetadata;
};

type MetadataProps = GenericMetadataProps &
(
| {
type: 'article';
publishedTime?: string;
authors?: null | string | URL | Array<string | URL>;
tags?: null | string | Array<string>;
}
| {
type: 'website';
}
);

export async function generateSanityMetadata(
client: SessionSanityClient,
props: MetadataProps
): Promise<Metadata> {
const { seo, parentMetadata, type } = props;

/** Base SEO */
const title =
seo?.metaTitle ||
seo?.openGraph?.title ||
parentMetadata?.title ||
parentMetadata?.openGraph?.title;

const description =
seo?.metaDescription ||
seo?.openGraph?.description ||
parentMetadata?.description ||
parentMetadata?.openGraph?.description;

const keywords = seo?.seoKeywords?.filter((keyword) => !!keyword);

/** Open Graph */
const ogTitle = seo?.openGraph?.title || parentMetadata?.openGraph?.title;
const ogDescription = seo?.openGraph?.description || parentMetadata?.openGraph?.description;

const sanityOgImage = seo?.openGraph?.image
? getSanityImageUrlBuilder(client, seo.openGraph.image)
.width(1200)
.height(630)
.fit('crop')
.url()
: null;

const sanityMetaImage = seo?.metaImage
? getSanityImageUrlBuilder(client, seo.metaImage).width(1200).height(630).fit('crop').url()
: null;

const ogImage = sanityOgImage || sanityMetaImage || parentMetadata?.openGraph?.images?.[0];

return {
title: title,
description: description,
keywords: keywords?.length ? keywords : undefined,
openGraph: {
title: ogTitle,
description: ogDescription,
images: ogImage,
...(type === 'article'
? {
type: 'article',
publishedTime: props.publishedTime,
authors: props.authors,
tags: props.tags,
}
: {
type: 'website',
}),
},
};
}
1 change: 1 addition & 0 deletions packages/sanity-cms/queries/getPost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const QUERY_GET_POSTS_WITH_SLUG = groq`*[_type == 'post' && slug.current == $slu
export type FormattedPostType = Omit<PostSchemaType, 'author' | 'date'> & {
author: AuthorSchemaType | undefined;
date: Date;
tags: Array<string>;
};
type QUERY_GET_POSTS_WITH_SLUG_RETURN_TYPE = Array<FormattedPostType>;

Expand Down
1 change: 1 addition & 0 deletions packages/sanity-cms/schemas/fields/basic/seo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type SeoType = {
_type?: 'seo';
nofollowAttributes?: boolean;
metaDescription?: string;
metaImage?: CustomImageType;
additionalMetaTags?: MetaTagType[];
metaTitle?: string;
seoKeywords?: string[];
Expand Down
18 changes: 17 additions & 1 deletion packages/sanity-cms/schemas/site.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,18 @@ const siteLinkFields = [
}),
];

export const siteFields = [...siteLinkFields, ...legalFields, seoField];
export const siteFields = [
...siteLinkFields,
...legalFields,
seoField,
defineField({
title: 'Blog SEO',
name: 'blogSeo',
description: 'SEO fields for Blog grid page and default SEO for blog posts',
type: 'seoMetaFields',
group: 'blogSeo',
}),
];

export const siteSchema = defineType({
type: 'document',
Expand All @@ -85,6 +96,11 @@ export const siteSchema = defineType({
icon: RobotIcon,
name: 'seo',
},
{
title: 'Blog SEO',
icon: RobotIcon,
name: 'blogSeo',
},
{
title: 'Header',
name: 'header',
Expand Down

0 comments on commit f9969b2

Please sign in to comment.