feat(SW-200): refactored breadcrumbs fetching and added json schema to layout

This commit is contained in:
Erik Tiekstra
2024-11-14 13:18:35 +01:00
parent 28738d7161
commit b22888db5f
13 changed files with 318 additions and 379 deletions

View File

@@ -1,3 +1,5 @@
import { getBreadcrumbsListSchema } from "@/utils/getJsonSchemas"
import styles from "./layout.module.css"
import {
@@ -7,7 +9,9 @@ import {
UIDParams,
} from "@/types/params"
export default function ContentTypeLayout({
export { generateMetadata } from "@/utils/generateMetadata"
export default async function ContentTypeLayout({
breadcrumbs,
preview,
children,
@@ -17,13 +21,25 @@ export default function ContentTypeLayout({
preview: React.ReactNode
}
>) {
const breadcrumbsListSchema = await getBreadcrumbsListSchema()
return (
<div className={styles.container}>
<section className={styles.layout}>
{preview}
{breadcrumbs}
{children}
</section>
</div>
<>
{breadcrumbsListSchema ? (
<script
type={breadcrumbsListSchema.type}
dangerouslySetInnerHTML={{
__html: JSON.stringify(breadcrumbsListSchema.jsonLd),
}}
/>
) : null}
<div className={styles.container}>
<section className={styles.layout}>
{preview}
{breadcrumbs}
{children}
</section>
</div>
</>
)
}

View File

@@ -17,8 +17,6 @@ import {
UIDParams,
} from "@/types/params"
export { generateMetadata } from "@/utils/generateMetadata"
export default function ContentTypePage({
params,
}: PageArgs<LangParams & ContentTypeParams & UIDParams, {}>) {

View File

@@ -1,4 +1,5 @@
#import "./AccountPage.graphql"
#import "./CollectionPage.graphql"
#import "./ContentPage.graphql"
#import "./LoyaltyPage.graphql"
@@ -9,6 +10,7 @@ fragment Breadcrumbs on Breadcrumbs {
node {
__typename
...AccountPageBreadcrumb
...CollectionPageBreadcrumb
...ContentPageBreadcrumb
...LoyaltyPageBreadcrumb
}
@@ -23,6 +25,7 @@ fragment BreadcrumbsRefs on Breadcrumbs {
node {
__typename
...AccountPageBreadcrumbRef
...CollectionPageBreadcrumbRef
...ContentPageBreadcrumbRef
...LoyaltyPageBreadcrumbRef
}

View File

@@ -0,0 +1,24 @@
#import "../System.graphql"
fragment CollectionPageBreadcrumb on CollectionPage {
web {
breadcrumbs {
title
}
}
system {
...System
}
url
}
fragment CollectionPageBreadcrumbRef on CollectionPage {
web {
breadcrumbs {
title
}
}
system {
...System
}
}

10
package-lock.json generated
View File

@@ -81,6 +81,7 @@
"lint-staged": "^15.2.2",
"netlify-plugin-cypress": "^2.2.1",
"prettier": "^3.2.5",
"schema-dts": "^1.1.2",
"start-server-and-test": "^2.0.3",
"ts-node": "^10.9.2",
"typescript": "^5",
@@ -17973,6 +17974,15 @@
"loose-envify": "^1.1.0"
}
},
"node_modules/schema-dts": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/schema-dts/-/schema-dts-1.1.2.tgz",
"integrity": "sha512-MpNwH0dZJHinVxk9bT8XUdjKTxMYrA5bLtrrGmFA6PTLwlOKnhi67XoRd6/ty+Djt6ZC0slR57qFhZDNMI6DhQ==",
"dev": true,
"peerDependencies": {
"typescript": ">=4.1.0"
}
},
"node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",

View File

@@ -96,6 +96,7 @@
"lint-staged": "^15.2.2",
"netlify-plugin-cypress": "^2.2.1",
"prettier": "^3.2.5",
"schema-dts": "^1.1.2",
"start-server-and-test": "^2.0.3",
"ts-node": "^10.9.2",
"typescript": "^5",

View File

@@ -1,16 +1,11 @@
import { z } from "zod"
import { removeMultipleSlashes } from "@/utils/url"
import { systemSchema } from "../schemas/system"
import { homeBreadcrumbs } from "./utils"
export const getBreadcrumbsSchema = z.array(
z.object({
href: z.string().optional(),
title: z.string(),
uid: z.string(),
})
)
const breadcrumbsRefs = z.object({
export const breadcrumbsRefsSchema = z.object({
web: z
.object({
breadcrumbs: z
@@ -32,43 +27,7 @@ const breadcrumbsRefs = z.object({
system: systemSchema,
})
export type BreadcrumbsRefs = z.infer<typeof breadcrumbsRefs>
export const validateMyPagesBreadcrumbsRefsContentstackSchema = z.object({
account_page: breadcrumbsRefs,
})
export type GetMyPagesBreadcrumbsRefsData = z.infer<
typeof validateMyPagesBreadcrumbsRefsContentstackSchema
>
export const validateLoyaltyPageBreadcrumbsRefsContentstackSchema = z.object({
loyalty_page: breadcrumbsRefs,
})
export type GetLoyaltyPageBreadcrumbsRefsData = z.infer<
typeof validateLoyaltyPageBreadcrumbsRefsContentstackSchema
>
export const validateCollectionPageBreadcrumbsRefsContentstackSchema = z.object(
{
collection_page: breadcrumbsRefs,
}
)
export type GetCollectionPageBreadcrumbsRefsData = z.infer<
typeof validateCollectionPageBreadcrumbsRefsContentstackSchema
>
export const validateContentPageBreadcrumbsRefsContentstackSchema = z.object({
content_page: breadcrumbsRefs,
})
export type GetContentPageBreadcrumbsRefsData = z.infer<
typeof validateContentPageBreadcrumbsRefsContentstackSchema
>
const page = z.object({
export const rawBreadcrumbsDataSchema = z.object({
web: z.object({
breadcrumbs: z.object({
title: z.string(),
@@ -92,36 +51,24 @@ const page = z.object({
system: systemSchema,
})
export type Page = z.infer<typeof page>
export const breadcrumbsSchema = rawBreadcrumbsDataSchema.transform((data) => {
const { parentsConnection, title } = data.web.breadcrumbs
const parentBreadcrumbs = parentsConnection.edges.map((breadcrumb) => {
return {
href: removeMultipleSlashes(
`/${breadcrumb.node.system.locale}/${breadcrumb.node.url}`
),
title: breadcrumb.node.web.breadcrumbs.title,
uid: breadcrumb.node.system.uid,
}
})
export const validateMyPagesBreadcrumbsContentstackSchema = z.object({
account_page: page,
const pageBreadcrumb = {
title,
uid: data.system.uid,
href: undefined,
}
const homeBreadcrumb = homeBreadcrumbs[data.system.locale]
return [homeBreadcrumb, parentBreadcrumbs, pageBreadcrumb].flat()
})
export type GetMyPagesBreadcrumbsData = z.infer<
typeof validateMyPagesBreadcrumbsContentstackSchema
>
export const validateLoyaltyPageBreadcrumbsContentstackSchema = z.object({
loyalty_page: page,
})
export type GetLoyaltyPageBreadcrumbsData = z.infer<
typeof validateLoyaltyPageBreadcrumbsContentstackSchema
>
export const validateContentPageBreadcrumbsContentstackSchema = z.object({
content_page: page,
})
export type GetContentPageBreadcrumbsData = z.infer<
typeof validateContentPageBreadcrumbsContentstackSchema
>
export const validateCollectionPageBreadcrumbsContentstackSchema = z.object({
collection_page: page,
})
export type GetCollectionPageBreadcrumbsData = z.infer<
typeof validateCollectionPageBreadcrumbsContentstackSchema
>

View File

@@ -1,3 +1,7 @@
import { metrics } from "@opentelemetry/api"
import { cache } from "react"
import { Lang } from "@/constants/languages"
import {
GetMyPagesBreadcrumbs,
GetMyPagesBreadcrumbsRefs,
@@ -14,237 +18,198 @@ import {
GetLoyaltyPageBreadcrumbs,
GetLoyaltyPageBreadcrumbsRefs,
} from "@/lib/graphql/Query/Breadcrumbs/LoyaltyPage.graphql"
import { request } from "@/lib/graphql/request"
import { notFound } from "@/server/errors/trpc"
import { contentstackExtendedProcedureUID, router } from "@/server/trpc"
import {
GetCollectionPageBreadcrumbsData,
GetCollectionPageBreadcrumbsRefsData,
type GetContentPageBreadcrumbsData,
type GetContentPageBreadcrumbsRefsData,
type GetLoyaltyPageBreadcrumbsData,
type GetLoyaltyPageBreadcrumbsRefsData,
type GetMyPagesBreadcrumbsData,
type GetMyPagesBreadcrumbsRefsData,
validateCollectionPageBreadcrumbsContentstackSchema,
validateCollectionPageBreadcrumbsRefsContentstackSchema,
validateContentPageBreadcrumbsContentstackSchema,
validateContentPageBreadcrumbsRefsContentstackSchema,
validateLoyaltyPageBreadcrumbsContentstackSchema,
validateLoyaltyPageBreadcrumbsRefsContentstackSchema,
validateMyPagesBreadcrumbsContentstackSchema,
validateMyPagesBreadcrumbsRefsContentstackSchema,
} from "./output"
import {
getBreadcrumbs,
getRefsResponse,
getResponse,
getTags,
Variables,
} from "./utils"
import { breadcrumbsRefsSchema, breadcrumbsSchema } from "./output"
import { getTags } from "./utils"
import { PageTypeEnum } from "@/types/requests/pageType"
import {
BreadcrumbsRefsSchema,
RawBreadcrumbsSchema,
} from "@/types/trpc/routers/contentstack/breadcrumbs"
async function getLoyaltyPageBreadcrumbs(variables: Variables) {
const refsResponse = await getRefsResponse<GetLoyaltyPageBreadcrumbsRefsData>(
GetLoyaltyPageBreadcrumbsRefs,
variables
)
const meter = metrics.getMeter("trpc.breadcrumbs")
const validatedRefsData =
validateLoyaltyPageBreadcrumbsRefsContentstackSchema.safeParse(
refsResponse.data
)
// OpenTelemetry metrics
const getBreadcrumbsRefsCounter = meter.createCounter(
"trpc.contentstack.breadcrumbs.refs.get"
)
const getBreadcrumbsRefsSuccessCounter = meter.createCounter(
"trpc.contentstack.breadcrumbs.refs.get-success"
)
const getBreadcrumbsRefsFailCounter = meter.createCounter(
"trpc.contentstack.breadcrumbs.refs.get-fail"
)
const getBreadcrumbsCounter = meter.createCounter(
"trpc.contentstack.breadcrumbs.get"
)
const getBreadcrumbsSuccessCounter = meter.createCounter(
"trpc.contentstack.breadcrumbs.get-success"
)
const getBreadcrumbsFailCounter = meter.createCounter(
"trpc.contentstack.breadcrumbs.get-fail"
)
if (!validatedRefsData.success) {
console.error(
`Failed to validate Loyaltypage Breadcrumbs Refs - (uid: ${variables.uid})`
)
console.error(validatedRefsData.error)
return null
}
const tags = getTags(validatedRefsData.data.loyalty_page, variables)
const response = await getResponse<GetLoyaltyPageBreadcrumbsData>(
GetLoyaltyPageBreadcrumbs,
variables,
tags
)
if (!response.data.loyalty_page.web?.breadcrumbs?.title) {
return null
}
const validatedBreadcrumbsData =
validateLoyaltyPageBreadcrumbsContentstackSchema.safeParse(response.data)
if (!validatedBreadcrumbsData.success) {
console.error(
`Failed to validate Loyaltypage Breadcrumbs Data - (uid: ${variables.uid})`
)
console.error(validatedBreadcrumbsData.error)
return null
}
return getBreadcrumbs(
validatedBreadcrumbsData.data.loyalty_page,
variables.locale
)
interface BreadcrumbsPageData<T> {
dataKey: keyof T
refQuery: string
query: string
}
async function getCollectionPageBreadcrumbs(variables: Variables) {
const refsResponse =
await getRefsResponse<GetCollectionPageBreadcrumbsRefsData>(
GetCollectionPageBreadcrumbsRefs,
variables
)
const validatedRefsData =
validateCollectionPageBreadcrumbsRefsContentstackSchema.safeParse(
refsResponse.data
)
const getBreadcrumbs = cache(async function fetchMemoizedBreadcrumbs<T>(
{ dataKey, refQuery, query }: BreadcrumbsPageData<T>,
{ uid, lang }: { uid: string; lang: Lang }
) {
getBreadcrumbsRefsCounter.add(1, { lang, uid })
console.info(
"contentstack.breadcrumbs refs get start",
JSON.stringify({ query: { lang, uid } })
)
const refsResponse = await request<{ [K in keyof T]: BreadcrumbsRefsSchema }>(
refQuery,
{ locale: lang, uid }
)
const validatedRefsData = breadcrumbsRefsSchema.safeParse(
refsResponse.data[dataKey]
)
if (!validatedRefsData.success) {
getBreadcrumbsRefsFailCounter.add(1, {
error_type: "validation_error",
error: JSON.stringify(validatedRefsData.error),
})
console.error(
`Failed to validate CollectionPpage Breadcrumbs Refs - (uid: ${variables.uid})`
"contentstack.breadcrumbs refs validation error",
JSON.stringify({
error: validatedRefsData.error,
})
)
console.error(validatedRefsData.error)
return null
}
const tags = getTags(validatedRefsData.data.collection_page, variables)
const response = await getResponse<GetCollectionPageBreadcrumbsData>(
GetCollectionPageBreadcrumbs,
variables,
tags
)
if (!response.data.collection_page.web?.breadcrumbs?.title) {
return null
}
const validatedBreadcrumbsData =
validateCollectionPageBreadcrumbsContentstackSchema.safeParse(response.data)
if (!validatedBreadcrumbsData.success) {
console.error(
`Failed to validate Collectionpage Breadcrumbs Data - (uid: ${variables.uid})`
)
console.error(validatedBreadcrumbsData.error)
return null
}
return getBreadcrumbs(
validatedBreadcrumbsData.data.collection_page,
variables.locale
)
}
async function getContentPageBreadcrumbs(variables: Variables) {
const refsResponse = await getRefsResponse<GetContentPageBreadcrumbsRefsData>(
GetContentPageBreadcrumbsRefs,
variables
)
const validatedRefsData =
validateContentPageBreadcrumbsRefsContentstackSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
console.error(
`Failed to validate Contentpage Breadcrumbs Refs - (uid: ${variables.uid})`
)
console.error(validatedRefsData.error)
return null
}
const tags = getTags(validatedRefsData.data.content_page, variables)
const response = await getResponse<GetContentPageBreadcrumbsData>(
GetContentPageBreadcrumbs,
variables,
tags
)
if (!response.data.content_page.web?.breadcrumbs?.title) {
return null
}
const validatedBreadcrumbsData =
validateContentPageBreadcrumbsContentstackSchema.safeParse(response.data)
if (!validatedBreadcrumbsData.success) {
console.error(
`Failed to validate Contentpage Breadcrumbs Data - (uid: ${variables.uid})`
)
console.error(validatedBreadcrumbsData.error)
return null
}
return getBreadcrumbs(
validatedBreadcrumbsData.data.content_page,
variables.locale
)
}
async function getMyPagesBreadcrumbs(variables: Variables) {
const refsResponse = await getRefsResponse<GetMyPagesBreadcrumbsRefsData>(
GetMyPagesBreadcrumbsRefs,
variables
)
const validatedRefsData =
validateMyPagesBreadcrumbsRefsContentstackSchema.safeParse(
refsResponse.data
)
if (!validatedRefsData.success) {
console.error(
`Failed to validate My Page Breadcrumbs Refs - (uid: ${variables.uid})`
)
console.error(validatedRefsData.error)
return null
}
const tags = getTags(validatedRefsData.data.account_page, variables)
const response = await getResponse<GetMyPagesBreadcrumbsData>(
GetMyPagesBreadcrumbs,
variables,
tags
)
if (!response.data.account_page.web?.breadcrumbs?.title) {
return []
}
const validatedBreadcrumbsData =
validateMyPagesBreadcrumbsContentstackSchema.safeParse(response.data)
getBreadcrumbsRefsSuccessCounter.add(1, { lang, uid })
console.info(
"contentstack.breadcrumbs refs get success",
JSON.stringify({ query: { lang, uid } })
)
if (!validatedBreadcrumbsData.success) {
const tags = getTags(validatedRefsData.data, lang)
getBreadcrumbsCounter.add(1, { lang, uid })
console.info(
"contentstack.breadcrumbs get start",
JSON.stringify({ query: { lang, uid } })
)
const response = await request<T>(
query,
{ locale: lang, uid },
{
cache: "force-cache",
next: { tags },
}
)
if (!response.data) {
const notFoundError = notFound(response)
getBreadcrumbsFailCounter.add(1, {
lang,
uid,
error_type: "not_found",
error: JSON.stringify({ code: notFoundError.code }),
})
console.error(
`Failed to validate My Page Breadcrumbs Data - (uid: ${variables.uid})`
"contentstack.breadcrumbs get not found error",
JSON.stringify({
query: { lang, uid },
error: { code: notFoundError.code },
})
)
console.error(validatedBreadcrumbsData.error)
return null
throw notFoundError
}
return getBreadcrumbs(
validatedBreadcrumbsData.data.account_page,
variables.locale
const validatedBreadcrumbs = breadcrumbsSchema.safeParse(
response.data[dataKey]
)
}
if (!validatedBreadcrumbs.success) {
getBreadcrumbsFailCounter.add(1, {
error_type: "validation_error",
error: JSON.stringify(validatedBreadcrumbs.error),
})
console.error(
"contentstack.breadcrumbs validation error",
JSON.stringify({
error: validatedBreadcrumbs.error,
})
)
return []
}
getBreadcrumbsSuccessCounter.add(1, { lang, uid })
console.info(
"contentstack.breadcrumbs get success",
JSON.stringify({ query: { lang, uid } })
)
return validatedBreadcrumbs.data
})
export const breadcrumbsQueryRouter = router({
get: contentstackExtendedProcedureUID.query(async ({ ctx }) => {
const variables = {
locale: ctx.lang,
lang: ctx.lang,
uid: ctx.uid,
}
switch (ctx.contentType) {
case PageTypeEnum.accountPage:
return await getMyPagesBreadcrumbs(variables)
return await getBreadcrumbs<{
account_page: RawBreadcrumbsSchema
}>(
{
dataKey: "account_page",
refQuery: GetMyPagesBreadcrumbsRefs,
query: GetMyPagesBreadcrumbs,
},
variables
)
case PageTypeEnum.collectionPage:
return await getCollectionPageBreadcrumbs(variables)
return await getBreadcrumbs<{
collection_page: RawBreadcrumbsSchema
}>(
{
dataKey: "collection_page",
refQuery: GetCollectionPageBreadcrumbsRefs,
query: GetCollectionPageBreadcrumbs,
},
variables
)
case PageTypeEnum.contentPage:
return await getContentPageBreadcrumbs(variables)
return await getBreadcrumbs<{
content_page: RawBreadcrumbsSchema
}>(
{
dataKey: "content_page",
refQuery: GetContentPageBreadcrumbsRefs,
query: GetContentPageBreadcrumbs,
},
variables
)
case PageTypeEnum.loyaltyPage:
return await getLoyaltyPageBreadcrumbs(variables)
return await getBreadcrumbs<{
loyalty_page: RawBreadcrumbsSchema
}>(
{
dataKey: "loyalty_page",
refQuery: GetLoyaltyPageBreadcrumbsRefs,
query: GetLoyaltyPageBreadcrumbs,
},
variables
)
default:
return []
}

View File

@@ -1,33 +1,22 @@
import { Lang } from "@/constants/languages"
import { request } from "@/lib/graphql/request"
import { internalServerError, notFound } from "@/server/errors/trpc"
import {
generateRefsResponseTag,
generateTag,
generateTags,
} from "@/utils/generateTag"
import { removeMultipleSlashes } from "@/utils/url"
import { type BreadcrumbsRefs, getBreadcrumbsSchema, Page } from "./output"
import { generateTag, generateTags } from "@/utils/generateTag"
import type { Edges } from "@/types/requests/utils/edges"
import type { NodeRefs } from "@/types/requests/utils/refs"
import { BreadcrumbsRefsSchema } from "@/types/trpc/routers/contentstack/breadcrumbs"
export function getConnections(refs: BreadcrumbsRefs) {
const connections: Edges<NodeRefs>[] = []
if (refs.web?.breadcrumbs) {
connections.push(refs.web.breadcrumbs.parentsConnection)
}
return connections
export type Variables = {
lang: Lang
uid: string
}
export const affix = "breadcrumbs"
// TODO: Make these editable in CMS?
export const homeBreadcrumbs = {
export const homeBreadcrumbs: {
[key in keyof typeof Lang]: { href: string; title: string; uid: string }
} = {
[Lang.da]: {
href: "/da",
title: "Hjem",
@@ -60,76 +49,19 @@ export const homeBreadcrumbs = {
},
}
export type Variables = {
locale: Lang
uid: string
}
export function getConnections(data: BreadcrumbsRefsSchema) {
const connections: Edges<NodeRefs>[] = []
export async function getRefsResponse<T>(query: string, variables: Variables) {
const refsResponse = await request<T>(query, variables, {
cache: "force-cache",
next: {
tags: [generateRefsResponseTag(variables.locale, variables.uid, affix)],
},
})
if (!refsResponse.data) {
throw notFound(refsResponse)
if (data.web?.breadcrumbs) {
connections.push(data.web.breadcrumbs.parentsConnection)
}
return refsResponse
return connections
}
export function getTags(page: BreadcrumbsRefs, variables: Variables) {
const connections = getConnections(page)
const tags = generateTags(variables.locale, connections)
tags.push(generateTag(variables.locale, page.system.uid, affix))
export function getTags(data: BreadcrumbsRefsSchema, lang: Lang) {
const connections = getConnections(data)
const tags = generateTags(lang, connections)
tags.push(generateTag(lang, data.system.uid, affix))
return tags
}
export async function getResponse<T>(
query: string,
variables: Variables,
tags: string[]
) {
const response = await request<T>(query, variables, {
cache: "force-cache",
next: { tags },
})
if (!response.data) {
throw notFound(response)
}
return response
}
export function getBreadcrumbs(page: Page, lang: Lang) {
const parentBreadcrumbs = page.web.breadcrumbs.parentsConnection.edges.map(
(breadcrumb) => {
return {
href: removeMultipleSlashes(
`/${breadcrumb.node.system.locale}/${breadcrumb.node.url}`
),
title: breadcrumb.node.web.breadcrumbs.title,
uid: breadcrumb.node.system.uid,
}
}
)
const pageBreadcrumb = {
title: page.web.breadcrumbs.title,
uid: page.system.uid,
}
const breadcrumbs = [
homeBreadcrumbs[lang],
parentBreadcrumbs,
pageBreadcrumb,
].flat()
const validatedBreadcrumbs = getBreadcrumbsSchema.safeParse(breadcrumbs)
if (!validatedBreadcrumbs.success) {
throw internalServerError(validatedBreadcrumbs.error)
}
return validatedBreadcrumbs.data
}

View File

@@ -1,5 +0,0 @@
import { z } from "zod"
import { getBreadcrumbsSchema } from "@/server/routers/contentstack/breadcrumbs/output"
export interface Breadcrumbs extends z.infer<typeof getBreadcrumbsSchema> {}

View File

@@ -0,0 +1,15 @@
import { z } from "zod"
import {
breadcrumbsRefsSchema,
breadcrumbsSchema,
rawBreadcrumbsDataSchema,
} from "@/server/routers/contentstack/breadcrumbs/output"
export interface BreadcrumbsRefsSchema
extends z.input<typeof breadcrumbsRefsSchema> {}
export interface RawBreadcrumbsSchema
extends z.input<typeof rawBreadcrumbsDataSchema> {}
export interface Breadcrumbs extends z.output<typeof breadcrumbsSchema> {}

View File

@@ -1,10 +1,6 @@
import { z } from "zod"
import {
metaDataSchema,
rawMetaDataDataSchema,
} from "@/server/routers/contentstack/metadata/output"
import { rawMetaDataDataSchema } from "@/server/routers/contentstack/metadata/output"
export interface RawMetaDataSchema
extends z.input<typeof rawMetaDataDataSchema> {}
export interface MetaDataSchema extends z.output<typeof metaDataSchema> {}

37
utils/getJsonSchemas.ts Normal file
View File

@@ -0,0 +1,37 @@
import { env } from "@/env/server"
import { serverClient } from "@/lib/trpc/server"
import type { BreadcrumbList, ListItem, WithContext } from "schema-dts"
import { Breadcrumbs } from "@/types/trpc/routers/contentstack/breadcrumbs"
function generateBreadcrumbsSchema(breadcrumbs: Breadcrumbs) {
if (!breadcrumbs.length) {
return null
}
const itemListElement: ListItem[] = breadcrumbs.map((item, index) => ({
"@type": "ListItem",
position: index + 1,
name: item.title,
// Only include "item" if "href" exists; otherwise, omit it
...(item.href ? { item: `${env.PUBLIC_URL}${item.href}` } : {}),
}))
const jsonLd: WithContext<BreadcrumbList> = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement,
}
return {
key: "breadcrumbs",
type: "application/ld+json",
jsonLd,
}
}
export async function getBreadcrumbsListSchema() {
const breadcrumbs = await serverClient().contentstack.breadcrumbs.get()
return generateBreadcrumbsSchema(breadcrumbs)
}