Merged in feat/SW-1845-metadata-hreflang (pull request #1504)
feat(SW-1845): Added alternates to metadata * feat(SW-1845): Added alternates to metadata Approved-by: Linus Flood
This commit is contained in:
@@ -1,15 +1,12 @@
|
|||||||
import { baseUrls } from "@/constants/routes/baseUrls"
|
|
||||||
import { findMyBooking } from "@/constants/routes/findMyBooking"
|
|
||||||
import { hotelreservation } from "@/constants/routes/hotelReservation"
|
|
||||||
import { publicProcedure, router } from "@/server/trpc"
|
import { publicProcedure, router } from "@/server/trpc"
|
||||||
|
|
||||||
import { getUidAndContentTypeByPath } from "@/services/cms/getUidAndContentTypeByPath"
|
import { getUidAndContentTypeByPath } from "@/services/cms/getUidAndContentTypeByPath"
|
||||||
|
|
||||||
|
import { getNonContentstackUrls } from "../metadata/output"
|
||||||
import { getLanguageSwitcherInput } from "./input"
|
import { getLanguageSwitcherInput } from "./input"
|
||||||
import { getUrlsOfAllLanguages } from "./utils"
|
import { getUrlsOfAllLanguages } from "./utils"
|
||||||
|
|
||||||
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||||
import type { Lang } from "@/constants/languages"
|
|
||||||
|
|
||||||
export const languageSwitcherQueryRouter = router({
|
export const languageSwitcherQueryRouter = router({
|
||||||
get: publicProcedure
|
get: publicProcedure
|
||||||
@@ -17,29 +14,14 @@ export const languageSwitcherQueryRouter = router({
|
|||||||
.query(async ({ input }) => {
|
.query(async ({ input }) => {
|
||||||
const { pathName, lang } = input
|
const { pathName, lang } = input
|
||||||
const { uid, contentType } = await getUidAndContentTypeByPath(pathName)
|
const { uid, contentType } = await getUidAndContentTypeByPath(pathName)
|
||||||
|
let urls: LanguageSwitcherData | null = null
|
||||||
|
|
||||||
if (!uid || !contentType) {
|
if (!uid || !contentType) {
|
||||||
// we have pages that are not currently routed within contentstack context,
|
urls = getNonContentstackUrls(lang, pathName)
|
||||||
// therefor this fix is needed for some of these pages
|
} else {
|
||||||
if (Object.values(findMyBooking).includes(pathName)) {
|
urls = await getUrlsOfAllLanguages(lang, uid, contentType)
|
||||||
const urls: LanguageSwitcherData = {}
|
|
||||||
return {
|
|
||||||
lang,
|
|
||||||
urls: Object.entries(findMyBooking).reduce((acc, [lang, url]) => {
|
|
||||||
acc[lang as Lang] = { url }
|
|
||||||
return urls
|
|
||||||
}, urls),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (pathName.startsWith(hotelreservation(lang))) {
|
|
||||||
return { lang, urls: baseUrls }
|
|
||||||
}
|
|
||||||
|
|
||||||
return { lang, urls: { [lang]: { url: pathName } } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const urls = await getUrlsOfAllLanguages(lang, uid, contentType)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
lang,
|
lang,
|
||||||
urls,
|
urls,
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { baseUrls } from "@/constants/routes/baseUrls"
|
||||||
|
import { findMyBooking } from "@/constants/routes/findMyBooking"
|
||||||
|
import { hotelreservation } from "@/constants/routes/hotelReservation"
|
||||||
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
import { attributesSchema as hotelAttributesSchema } from "../../hotels/schemas/hotel"
|
import { attributesSchema as hotelAttributesSchema } from "../../hotels/schemas/hotel"
|
||||||
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
||||||
import { systemSchema } from "../schemas/system"
|
import { systemSchema } from "../schemas/system"
|
||||||
@@ -8,7 +13,9 @@ import { getDescription, getImage, getTitle } from "./utils"
|
|||||||
import type { Metadata } from "next"
|
import type { Metadata } from "next"
|
||||||
|
|
||||||
import { Country } from "@/types/enums/country"
|
import { Country } from "@/types/enums/country"
|
||||||
|
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||||
import { RTETypeEnum } from "@/types/rte/enums"
|
import { RTETypeEnum } from "@/types/rte/enums"
|
||||||
|
import type { Lang } from "@/constants/languages"
|
||||||
|
|
||||||
const metaDataJsonSchema = z.object({
|
const metaDataJsonSchema = z.object({
|
||||||
children: z.array(
|
children: z.array(
|
||||||
@@ -101,6 +108,7 @@ export const metadataSchema = rawMetadataSchema.transform(async (data) => {
|
|||||||
const noIndex = !!data.web?.seo_metadata?.noindex
|
const noIndex = !!data.web?.seo_metadata?.noindex
|
||||||
|
|
||||||
const metadata: Metadata = {
|
const metadata: Metadata = {
|
||||||
|
metadataBase: new URL(env.PUBLIC_URL),
|
||||||
title: await getTitle(data),
|
title: await getTitle(data),
|
||||||
description: getDescription(data),
|
description: getDescription(data),
|
||||||
openGraph: {
|
openGraph: {
|
||||||
@@ -116,3 +124,21 @@ export const metadataSchema = rawMetadataSchema.transform(async (data) => {
|
|||||||
}
|
}
|
||||||
return metadata
|
return metadata
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Several pages are not currently routed within contentstack context.
|
||||||
|
// This function is used to generate the urls for these pages.
|
||||||
|
export function getNonContentstackUrls(lang: Lang, pathName: string) {
|
||||||
|
if (Object.values(findMyBooking).includes(pathName)) {
|
||||||
|
const urls: LanguageSwitcherData = {}
|
||||||
|
return Object.entries(findMyBooking).reduce((acc, [lang, url]) => {
|
||||||
|
acc[lang as Lang] = { url }
|
||||||
|
return urls
|
||||||
|
}, urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathName.startsWith(hotelreservation(lang))) {
|
||||||
|
return baseUrls
|
||||||
|
}
|
||||||
|
|
||||||
|
return { [lang]: { url: pathName } }
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,11 +16,15 @@ import { contentStackUidWithServiceProcedure, router } from "@/server/trpc"
|
|||||||
import { generateTag } from "@/utils/generateTag"
|
import { generateTag } from "@/utils/generateTag"
|
||||||
|
|
||||||
import { getHotel } from "../../hotels/query"
|
import { getHotel } from "../../hotels/query"
|
||||||
|
import { getUrlsOfAllLanguages } from "../languageSwitcher/utils"
|
||||||
import { getMetadataInput } from "./input"
|
import { getMetadataInput } from "./input"
|
||||||
import { metadataSchema } from "./output"
|
import { getNonContentstackUrls, metadataSchema } from "./output"
|
||||||
import { affix, getCityData, getCountryData } from "./utils"
|
import { affix, getCityData, getCountryData } from "./utils"
|
||||||
|
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
|
||||||
import { PageContentTypeEnum } from "@/types/requests/contentType"
|
import { PageContentTypeEnum } from "@/types/requests/contentType"
|
||||||
|
import type { LanguageSwitcherData } from "@/types/requests/languageSwitcher"
|
||||||
import type { RawMetadataSchema } from "@/types/trpc/routers/contentstack/metadata"
|
import type { RawMetadataSchema } from "@/types/trpc/routers/contentstack/metadata"
|
||||||
import type { Lang } from "@/constants/languages"
|
import type { Lang } from "@/constants/languages"
|
||||||
|
|
||||||
@@ -92,7 +96,10 @@ const fetchMetadata = cache(async function fetchMemoizedMetadata<T>(
|
|||||||
return response.data
|
return response.data
|
||||||
})
|
})
|
||||||
|
|
||||||
async function getTransformedMetadata(data: unknown) {
|
async function getTransformedMetadata(
|
||||||
|
data: unknown,
|
||||||
|
alternates: Metadata["alternates"]
|
||||||
|
) {
|
||||||
transformMetadataCounter.add(1)
|
transformMetadataCounter.add(1)
|
||||||
console.info("contentstack.metadata transform start")
|
console.info("contentstack.metadata transform start")
|
||||||
const validatedMetadata = await metadataSchema.safeParseAsync(data)
|
const validatedMetadata = await metadataSchema.safeParseAsync(data)
|
||||||
@@ -114,6 +121,9 @@ async function getTransformedMetadata(data: unknown) {
|
|||||||
transformMetadataSuccessCounter.add(1)
|
transformMetadataSuccessCounter.add(1)
|
||||||
console.info("contentstack.metadata transform success")
|
console.info("contentstack.metadata transform success")
|
||||||
|
|
||||||
|
if (alternates) {
|
||||||
|
validatedMetadata.data.alternates = alternates
|
||||||
|
}
|
||||||
return validatedMetadata.data
|
return validatedMetadata.data
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,28 +136,66 @@ export const metadataQueryRouter = router({
|
|||||||
uid: ctx.uid,
|
uid: ctx.uid,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let urls: LanguageSwitcherData | null = null
|
||||||
|
if (
|
||||||
|
input.subpage ||
|
||||||
|
input.filterFromUrl ||
|
||||||
|
!ctx.uid ||
|
||||||
|
!ctx.contentType
|
||||||
|
) {
|
||||||
|
urls = getNonContentstackUrls(ctx.lang, `${ctx.lang}/${ctx.pathname}`)
|
||||||
|
} else {
|
||||||
|
urls = await getUrlsOfAllLanguages(ctx.lang, ctx.uid, ctx.contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
let alternates: Metadata["alternates"] = null
|
||||||
|
|
||||||
|
if (urls) {
|
||||||
|
const languages: Record<string, string> = {}
|
||||||
|
Object.entries(urls)
|
||||||
|
.filter(([lang]) => lang !== ctx.lang)
|
||||||
|
.forEach(([lang, { url }]) => {
|
||||||
|
languages[lang] = url
|
||||||
|
})
|
||||||
|
const canonical = urls[ctx.lang]?.url
|
||||||
|
alternates = {
|
||||||
|
canonical,
|
||||||
|
languages,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (ctx.contentType) {
|
switch (ctx.contentType) {
|
||||||
case PageContentTypeEnum.accountPage:
|
case PageContentTypeEnum.accountPage:
|
||||||
const accountPageResponse = await fetchMetadata<{
|
const accountPageResponse = await fetchMetadata<{
|
||||||
account_page: RawMetadataSchema
|
account_page: RawMetadataSchema
|
||||||
}>(GetAccountPageMetadata, variables)
|
}>(GetAccountPageMetadata, variables)
|
||||||
return getTransformedMetadata(accountPageResponse.account_page)
|
return getTransformedMetadata(
|
||||||
|
accountPageResponse.account_page,
|
||||||
|
alternates
|
||||||
|
)
|
||||||
case PageContentTypeEnum.collectionPage:
|
case PageContentTypeEnum.collectionPage:
|
||||||
const collectionPageResponse = await fetchMetadata<{
|
const collectionPageResponse = await fetchMetadata<{
|
||||||
collection_page: RawMetadataSchema
|
collection_page: RawMetadataSchema
|
||||||
}>(GetCollectionPageMetadata, variables)
|
}>(GetCollectionPageMetadata, variables)
|
||||||
return getTransformedMetadata(collectionPageResponse.collection_page)
|
return getTransformedMetadata(
|
||||||
|
collectionPageResponse.collection_page,
|
||||||
|
alternates
|
||||||
|
)
|
||||||
case PageContentTypeEnum.contentPage:
|
case PageContentTypeEnum.contentPage:
|
||||||
const contentPageResponse = await fetchMetadata<{
|
const contentPageResponse = await fetchMetadata<{
|
||||||
content_page: RawMetadataSchema
|
content_page: RawMetadataSchema
|
||||||
}>(GetContentPageMetadata, variables)
|
}>(GetContentPageMetadata, variables)
|
||||||
return getTransformedMetadata(contentPageResponse.content_page)
|
return getTransformedMetadata(
|
||||||
|
contentPageResponse.content_page,
|
||||||
|
alternates
|
||||||
|
)
|
||||||
case PageContentTypeEnum.destinationOverviewPage:
|
case PageContentTypeEnum.destinationOverviewPage:
|
||||||
const destinationOverviewPageResponse = await fetchMetadata<{
|
const destinationOverviewPageResponse = await fetchMetadata<{
|
||||||
destination_overview_page: RawMetadataSchema
|
destination_overview_page: RawMetadataSchema
|
||||||
}>(GetDestinationOverviewPageMetadata, variables)
|
}>(GetDestinationOverviewPageMetadata, variables)
|
||||||
return getTransformedMetadata(
|
return getTransformedMetadata(
|
||||||
destinationOverviewPageResponse.destination_overview_page
|
destinationOverviewPageResponse.destination_overview_page,
|
||||||
|
alternates
|
||||||
)
|
)
|
||||||
case PageContentTypeEnum.destinationCountryPage:
|
case PageContentTypeEnum.destinationCountryPage:
|
||||||
const destinationCountryPageResponse = await fetchMetadata<{
|
const destinationCountryPageResponse = await fetchMetadata<{
|
||||||
@@ -159,10 +207,13 @@ export const metadataQueryRouter = router({
|
|||||||
ctx.serviceToken,
|
ctx.serviceToken,
|
||||||
ctx.lang
|
ctx.lang
|
||||||
)
|
)
|
||||||
return getTransformedMetadata({
|
return getTransformedMetadata(
|
||||||
...destinationCountryPageResponse.destination_country_page,
|
{
|
||||||
...countryData,
|
...destinationCountryPageResponse.destination_country_page,
|
||||||
})
|
...countryData,
|
||||||
|
},
|
||||||
|
alternates
|
||||||
|
)
|
||||||
case PageContentTypeEnum.destinationCityPage:
|
case PageContentTypeEnum.destinationCityPage:
|
||||||
const destinationCityPageResponse = await fetchMetadata<{
|
const destinationCityPageResponse = await fetchMetadata<{
|
||||||
destination_city_page: RawMetadataSchema
|
destination_city_page: RawMetadataSchema
|
||||||
@@ -173,15 +224,21 @@ export const metadataQueryRouter = router({
|
|||||||
ctx.serviceToken,
|
ctx.serviceToken,
|
||||||
ctx.lang
|
ctx.lang
|
||||||
)
|
)
|
||||||
return getTransformedMetadata({
|
return getTransformedMetadata(
|
||||||
...destinationCityPageResponse.destination_city_page,
|
{
|
||||||
...cityData,
|
...destinationCityPageResponse.destination_city_page,
|
||||||
})
|
...cityData,
|
||||||
|
},
|
||||||
|
alternates
|
||||||
|
)
|
||||||
case PageContentTypeEnum.loyaltyPage:
|
case PageContentTypeEnum.loyaltyPage:
|
||||||
const loyaltyPageResponse = await fetchMetadata<{
|
const loyaltyPageResponse = await fetchMetadata<{
|
||||||
loyalty_page: RawMetadataSchema
|
loyalty_page: RawMetadataSchema
|
||||||
}>(GetLoyaltyPageMetadata, variables)
|
}>(GetLoyaltyPageMetadata, variables)
|
||||||
return getTransformedMetadata(loyaltyPageResponse.loyalty_page)
|
return getTransformedMetadata(
|
||||||
|
loyaltyPageResponse.loyalty_page,
|
||||||
|
alternates
|
||||||
|
)
|
||||||
case PageContentTypeEnum.hotelPage:
|
case PageContentTypeEnum.hotelPage:
|
||||||
const hotelPageResponse = await fetchMetadata<{
|
const hotelPageResponse = await fetchMetadata<{
|
||||||
hotel_page: RawMetadataSchema
|
hotel_page: RawMetadataSchema
|
||||||
@@ -198,10 +255,13 @@ export const metadataQueryRouter = router({
|
|||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
|
|
||||||
return getTransformedMetadata({
|
return getTransformedMetadata(
|
||||||
...hotelPageData,
|
{
|
||||||
hotelData: hotelData?.hotel,
|
...hotelPageData,
|
||||||
})
|
hotelData: hotelData?.hotel,
|
||||||
|
},
|
||||||
|
alternates
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user