* update env vars * update middleware * remove pages router and move pages to /app * move to /team * update imports * fix * remove pages router and move org pages to /app * wip * fix orgSlug/user pages * fix orgSlug/user/type pages * remove generateMetadata from embed pages * fix * remove pages router for /user pages * generateMetadata is not needed in embed pages * remove future/org * no layout in [user] page * simplify * fix * fix OG image for [user] * fix OG images for org/user and team/slug * fix OG images for booking page * fix all metadata * use isBrandingHidden * remove BookerSeo and its usages * rename excludeAppNameFromTitle -> hideBranding * remove logic for meeting type in HeadSeo * remove BookerSeo instances from team-view and users-public-view * create generateMeetingMetadata util and use it to reduce duplicate code * remove BookerSeo imports * fix spacing * remove constructMeetingImage mock from head-seo.test * fetch avatarUrl using user id for user page metadata * fix test * remove unused test cases * index and follow must be true by default * invert noindex/nofollow flags * remove HeadSeo for already migrated pages and refactor prepareMetadata * fix organization-settings.e2e.ts * fix order * enable parallel test execution for dynamic-booking e2e test * fix * + could be %2B in app router * refactor handling logic for embeds in app router * fix isEmbed * fix embed-core * remove dead code * move embed pages back to /future * add back embed pages in pages router * revert some changes * fix import type checks * simplify * fix * feat: Implement generateBookingPageMetadata function for improved SEO and metadata handling across user and event pages (#18440) - Added a new utility function `generateBookingPageMetadata` to streamline the generation of metadata for booking and user profile pages. - Updated multiple page components to utilize the new function, enhancing SEO indexing and metadata consistency. - Removed redundant code related to previous metadata generation methods, improving code clarity and maintainability. * fix dirs * Pr-review-fixes-app-router-team-pages (#18450) * Remove unnecessary getPublicEvent call * Remove unused variable * Fix 404 for team page (#18451) * Remove console log --------- Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
125 lines
3.9 KiB
TypeScript
125 lines
3.9 KiB
TypeScript
import { type TFunction } from "i18next";
|
|
import i18next from "i18next";
|
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
|
import { headers } from "next/headers";
|
|
|
|
import type { MeetingImageProps } from "@calcom/lib/OgImages";
|
|
import { constructGenericImage, constructMeetingImage } from "@calcom/lib/OgImages";
|
|
import { IS_CALCOM, WEBAPP_URL, APP_NAME, SEO_IMG_OGIMG, CAL_URL } from "@calcom/lib/constants";
|
|
import { buildCanonical } from "@calcom/lib/next-seo.config";
|
|
import { truncateOnWord } from "@calcom/lib/text";
|
|
//@ts-expect-error no type definitions
|
|
import config from "@calcom/web/next-i18next.config";
|
|
|
|
const i18nInstanceCache: Record<string, any> = {};
|
|
|
|
const createI18nInstance = async (locale: string, ns: string) => {
|
|
const cacheKey = `${locale}-${ns}`;
|
|
// Check module-level cache first
|
|
if (i18nInstanceCache[cacheKey]) {
|
|
return i18nInstanceCache[cacheKey];
|
|
}
|
|
|
|
const { _nextI18Next } = await serverSideTranslations(locale, [ns], config);
|
|
|
|
const _i18n = i18next.createInstance();
|
|
await _i18n.init({
|
|
lng: locale,
|
|
resources: _nextI18Next?.initialI18nStore,
|
|
fallbackLng: _nextI18Next?.userConfig?.i18n.defaultLocale,
|
|
});
|
|
|
|
// Cache the instance
|
|
i18nInstanceCache[cacheKey] = _i18n;
|
|
return _i18n;
|
|
};
|
|
|
|
const getTranslationWithCache = async (locale: string, ns = "common") => {
|
|
const localeWithFallback = locale ?? "en";
|
|
const i18n = await createI18nInstance(localeWithFallback, ns);
|
|
return i18n.getFixedT(localeWithFallback, ns);
|
|
};
|
|
|
|
export const getTranslate = async () => {
|
|
const headersList = await headers();
|
|
// If "x-locale" does not exist in header,
|
|
// ensure that config.matcher in middleware includes the page you are testing
|
|
const locale = headersList.get("x-locale");
|
|
const t = await getTranslationWithCache(locale ?? "en");
|
|
return t;
|
|
};
|
|
|
|
const _generateMetadataWithoutImage = async (
|
|
getTitle: (t: TFunction<string, undefined>) => string,
|
|
getDescription: (t: TFunction<string, undefined>) => string,
|
|
hideBranding?: boolean,
|
|
origin?: string
|
|
) => {
|
|
const h = headers();
|
|
const pathname = h.get("x-pathname") ?? "";
|
|
const canonical = buildCanonical({ path: pathname, origin: origin ?? CAL_URL });
|
|
const locale = h.get("x-locale") ?? "en";
|
|
const t = await getTranslationWithCache(locale);
|
|
|
|
const title = getTitle(t);
|
|
const description = getDescription(t);
|
|
const titleSuffix = `| ${APP_NAME}`;
|
|
const displayedTitle = title.includes(titleSuffix) || hideBranding ? title : `${title} ${titleSuffix}`;
|
|
const metadataBase = new URL(IS_CALCOM ? "https://cal.com" : WEBAPP_URL);
|
|
|
|
return {
|
|
title: title.length === 0 ? APP_NAME : displayedTitle,
|
|
description,
|
|
alternates: { canonical },
|
|
openGraph: {
|
|
description: truncateOnWord(description, 158),
|
|
url: canonical,
|
|
type: "website",
|
|
siteName: APP_NAME,
|
|
title: displayedTitle,
|
|
},
|
|
metadataBase,
|
|
};
|
|
};
|
|
|
|
export const _generateMetadata = async (
|
|
getTitle: (t: TFunction<string, undefined>) => string,
|
|
getDescription: (t: TFunction<string, undefined>) => string,
|
|
hideBranding?: boolean,
|
|
origin?: string
|
|
) => {
|
|
const metadata = await _generateMetadataWithoutImage(getTitle, getDescription, hideBranding, origin);
|
|
const image =
|
|
SEO_IMG_OGIMG +
|
|
constructGenericImage({
|
|
title: metadata.title,
|
|
description: metadata.description,
|
|
});
|
|
|
|
return {
|
|
...metadata,
|
|
openGraph: {
|
|
...metadata.openGraph,
|
|
images: [image],
|
|
},
|
|
};
|
|
};
|
|
|
|
export const generateMeetingMetadata = async (
|
|
meeting: MeetingImageProps,
|
|
getTitle: (t: TFunction<string, undefined>) => string,
|
|
getDescription: (t: TFunction<string, undefined>) => string,
|
|
hideBranding?: boolean,
|
|
origin?: string
|
|
) => {
|
|
const metadata = await _generateMetadataWithoutImage(getTitle, getDescription, hideBranding, origin);
|
|
const image = SEO_IMG_OGIMG + constructMeetingImage(meeting);
|
|
return {
|
|
...metadata,
|
|
openGraph: {
|
|
...metadata.openGraph,
|
|
images: [image],
|
|
},
|
|
};
|
|
};
|