chore: Remove HeadSeo everywhere (#20272)

This commit is contained in:
Benny Joo authored and GitHub committed 2025-03-21 14:24:53 -04:00
1 parent 78ddf71823
commit 4606fc6ace
28 files changed
+20 -356

No files matched your search

@@ -1,11 +1,7 @@
import Shell from "@calcom/features/shell/Shell";
const Layout = ({ children }: { children: React.ReactNode }) => {
return (
<Shell withoutSeo={true} withoutMain={true}>
{children}
</Shell>
);
return <Shell withoutMain={true}>{children}</Shell>;
};
export default Layout;
@@ -41,7 +41,7 @@ const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
const Component = await routingFormsComponents[mainPage as keyof typeof routingFormsComponents]();
const FinalComponent = () => (
<FormProvider>
<Component {...props as any} />
<Component {...(props as any)} />
</FormProvider>
);
@@ -50,7 +50,7 @@ const ServerPage = async ({ params, searchParams }: ServerPageProps) => {
}
return (
<Shell withoutMain withoutSeo>
<Shell withoutMain>
<FinalComponent />
</Shell>
);
@@ -8,7 +8,7 @@ export default async function InsightsLayout({ children }: { children: React.Rea
const t = await getTranslate();
return (
<div>
<Shell withoutMain={false} withoutSeo={true} heading={t("insights")} subtitle={t("insights_subtitle")}>
<Shell withoutMain={false} heading={t("insights")} subtitle={t("insights_subtitle")}>
<UpgradeTipWrapper>{children}</UpgradeTipWrapper>
</Shell>
</div>
@@ -740,7 +740,6 @@ export default function SettingsLayoutAppDirClient({
return (
<Shell
withoutSeo={true}
flexChildrenContainer
{...rest}
SidebarContainer={
+1 -1
View File
@@ -44,7 +44,7 @@ export default function EnterprisePage() {
];
return (
<div>
<Shell heading={t("enterprise")} subtitle={t("enterprise_description")} withoutSeo={true}>
<Shell heading={t("enterprise")} subtitle={t("enterprise_description")}>
<UpgradeTip
plan="enterprise"
title={t("create_your_org")}
+1 -1
View File
@@ -12,7 +12,7 @@ const ShellHeading = () => {
export default function WrappedApp(props: AppPageProps) {
return (
<Shell smallHeading isPublic heading={<ShellHeading />} backPath="/apps" withoutSeo>
<Shell smallHeading isPublic heading={<ShellHeading />} backPath="/apps">
{props.licenseRequired ? (
<LicenseRequired>
<AppPage {...props} />
@@ -16,7 +16,6 @@ export default function Apps({ apps, category }: CategoryDataProps) {
<>
<Shell
isPublic
withoutSeo
backPath="/apps"
title={t("app_store")}
description={t("app_store_description")}
@@ -16,7 +16,7 @@ export default function Apps({ categories }: PageProps) {
const { t, isLocaleReady } = useLocale();
return (
<Shell isPublic large title={t("app_store")} description={t("app_store_description")} withoutSeo>
<Shell isPublic large title={t("app_store")} description={t("app_store_description")}>
<div className="text-md flex items-center gap-1 px-4 pb-3 pt-3 font-normal md:px-8 lg:px-0 lg:pt-0">
<Link
href="/apps"
@@ -11,7 +11,6 @@ const MembersPage: React.FC = () => {
withoutMain={false}
title={t("organization_members")}
description={t("organization_description")}
withoutSeo
subtitle={t("organization_description")}>
<MembersView />
</Shell>
+1 -1
View File
@@ -7,7 +7,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
export default function MorePage() {
const { t } = useLocale();
return (
<Shell withoutSeo={true}>
<Shell>
<div className="max-w-screen-lg">
<MobileNavigationMoreItems />
<p className="text-subtle mt-6 text-xs leading-tight md:hidden">{t("more_page_footer")}</p>
@@ -56,7 +56,7 @@ export default function PlatformBillingUpgrade() {
if (!isPlatformUser)
return (
<div>
<Shell withoutSeo={true} isPlatformUser={true} withoutMain={false} SidebarContainer={<></>}>
<Shell isPlatformUser={true} withoutMain={false} SidebarContainer={<></>}>
<NoPlatformPlan />
</Shell>
</div>
@@ -68,7 +68,6 @@ export default function PlatformBillingUpgrade() {
heading={t("platform_billing")}
title={t("platform_billing")}
withoutMain={false}
withoutSeo={true}
subtitle={t("manage_billing_description")}
isPlatformUser={true}>
<>
@@ -68,7 +68,7 @@ export default function EditOAuthClient() {
if (isPlatformUser && isPaidUser) {
return (
<div>
<Shell withoutSeo={true} title={t("oAuth_client_updation_form")} isPlatformUser={true}>
<Shell title={t("oAuth_client_updation_form")} isPlatformUser={true}>
<div className="m-2 md:mx-14 md:mx-5">
<div className="border-subtle mx-auto block justify-between rounded-t-lg border px-4 py-6 sm:flex sm:px-6">
<div className="flex w-full flex-col">
@@ -116,7 +116,7 @@ export default function EditOAuthClient() {
return (
<div>
<Shell withoutSeo={true} isPlatformUser={true} withoutMain={false} SidebarContainer={<></>}>
<Shell isPlatformUser={true} withoutMain={false} SidebarContainer={<></>}>
<NoPlatformPlan />
</Shell>
</div>
@@ -42,7 +42,7 @@ export default function EditOAuthClientWebhooks() {
if (isPlatformUser && isPaidUser) {
return (
<div>
<Shell withoutSeo={true} title={t("webhook_update_form")} isPlatformUser={true}>
<Shell title={t("webhook_update_form")} isPlatformUser={true}>
<div className="m-2 md:mx-5">
<div className="border-subtle mx-auto block justify-between rounded-t-lg border px-4 py-6 sm:flex sm:px-6">
<div className="flex w-full flex-col">
@@ -126,7 +126,7 @@ export default function EditOAuthClientWebhooks() {
return (
<div>
<Shell withoutSeo={true} isPlatformUser={true} withoutMain={false} SidebarContainer={<></>}>
<Shell isPlatformUser={true} withoutMain={false} SidebarContainer={<></>}>
<NoPlatformPlan />
</Shell>
</div>
@@ -64,7 +64,7 @@ export default function CreateOAuthClient() {
if (isPlatformUser && isPaidUser) {
return (
<div>
<Shell title={t("oAuth_client_creation_form")} isPlatformUser={true} withoutSeo={true}>
<Shell title={t("oAuth_client_creation_form")} isPlatformUser={true}>
<div className="m-2 md:mx-5">
<div className="border-subtle mx-auto block justify-between rounded-t-lg border px-4 py-6 sm:flex sm:px-6">
<div className="flex w-full flex-col">
@@ -85,7 +85,7 @@ export default function CreateOAuthClient() {
return (
<div>
<Shell withoutSeo={true} isPlatformUser={true} withoutMain={false} SidebarContainer={<></>}>
<Shell isPlatformUser={true} withoutMain={false} SidebarContainer={<></>}>
<NoPlatformPlan />
</Shell>
</div>
@@ -20,7 +20,6 @@ export default function PlatformPlans() {
<div>
<Shell
backPath
withoutSeo={true}
isPlatformUser={true}
heading={
<h1 className="mx-2 mt-4 text-center text-xl md:text-2xl">
@@ -71,7 +71,6 @@ export default function Platform() {
subtitle={t("platform_description")}
title={t("platform")}
description={t("platform_description")}
withoutSeo={true}
withoutMain={false}
isPlatformUser={true}>
<HelpCards />
@@ -89,7 +88,6 @@ export default function Platform() {
// hence we pass isPlatformUser boolean as prop
isPlatformUser={true}
withoutMain={false}
withoutSeo={true}
SidebarContainer={<></>}>
<NoPlatformPlan />
</Shell>
-4
View File
@@ -151,10 +151,6 @@ vi.mock("@calcom/trpc/react", () => ({
},
}));
vi.mock("@calcom/ui/components/head-seo", () => ({
HeadSeo: vi.fn(),
}));
vi.mock("@calcom/ui/styles", () => ({
useCalcomTheme: vi.fn(),
}));
+1 -1
View File
@@ -30,7 +30,7 @@ export default function UpgradePage() {
const doesUserHaveOrgToUpgrade = trpc.viewer.organizations.checkIfOrgNeedsUpgrade.useQuery();
return (
<Shell withoutSeo={true}>
<Shell >
<div className="max-w-screen-lg">
{doesUserHaveOrgToUpgrade.data ? (
<EmptyScreen
@@ -3,7 +3,6 @@ import { describe, it, vi } from "vitest";
import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import { HeadSeo } from "@calcom/ui/components/head-seo";
import UserPage from "./users-public-view";
@@ -56,7 +55,7 @@ function mockedUserPageComponentProps(props: Partial<React.ComponentProps<typeof
}
describe("UserPage Component", () => {
it("should render HeadSeo with correct props", () => {
it("should render with no throw", () => {
const mockData = {
props: mockedUserPageComponentProps({
entity: {
@@ -74,7 +73,6 @@ describe("UserPage Component", () => {
uid: "uid",
});
render(<UserPage {...mockData.props} />);
expect(HeadSeo).not.toHaveBeenCalled();
expect(() => render(<UserPage {...mockData.props} />)).not.toThrow();
});
});
@@ -13,7 +13,6 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc";
import { Button } from "@calcom/ui/components/button";
import { Switch } from "@calcom/ui/components/form";
import { HeadSeo } from "@calcom/ui/components/head-seo";
import { Icon } from "@calcom/ui/components/icon";
import { showToast } from "@calcom/ui/components/toast";
@@ -204,7 +203,6 @@ function HitPaySetupPage(props: IHitPaySetupProps) {
return (
<>
<HeadSeo nextSeoProps={{ noindex: true, nofollow: true }} title="HitPay" description="" />
<div className="bg-default flex h-screen items-center justify-center">
{showContent ? (
<div className="flex w-full w-full max-w-[43em] flex-col items-center justify-center space-y-4 p-4 lg:space-y-5">
@@ -66,7 +66,7 @@ function WorkflowsPage({ filteredList }: PageProps) {
});
return (
<Shell withoutMain withoutSeo={true}>
<Shell withoutMain>
<LicenseRequired>
<ShellMain
heading={t("workflows")}
@@ -229,7 +229,7 @@ function WorkflowPage({
});
return session.data ? (
<Shell withoutSeo={true} withoutMain backPath="/workflows">
<Shell withoutMain backPath="/workflows">
<LicenseRequired>
<Form
form={form}
@@ -100,7 +100,6 @@ function EventTypeSingleLayout({
backPath="/event-types"
title={`${eventType.title} | ${t("event_type")}`}
heading={eventType.title}
withoutSeo={!isPlatform} // Metadata is handled by App Router Metadata API for Event Type Web Page
CTA={
<div className="flex items-center justify-end">
{!formMethods.getValues("metadata")?.managedEventConfig && (
-12
View File
@@ -10,13 +10,11 @@ import { useRedirectToLoginIfUnauthenticated } from "@calcom/features/auth/lib/h
import { useRedirectToOnboardingIfNeeded } from "@calcom/features/auth/lib/hooks/useRedirectToOnboardingIfNeeded";
import { KBarContent, KBarRoot } from "@calcom/features/kbar/Kbar";
import TimezoneChangeDialog from "@calcom/features/settings/TimezoneChangeDialog";
import { APP_NAME } from "@calcom/lib/constants";
import { useFormbricks } from "@calcom/lib/formbricks-client";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@calcom/ui/classNames";
import { Button } from "@calcom/ui/components/button";
import { ErrorBoundary } from "@calcom/ui/components/errorBoundary";
import { HeadSeo } from "@calcom/ui/components/head-seo";
import { SkeletonText } from "@calcom/ui/components/skeleton";
import { SideBarContainer } from "./SideBar";
@@ -30,19 +28,11 @@ const Layout = (props: LayoutProps) => {
const { banners, bannersHeight } = useBanners();
const pathname = usePathname();
const isFullPageWithoutSidebar = pathname?.startsWith("/apps/routing-forms/reporting/");
const pageTitle = typeof props.heading === "string" && !props.title ? props.heading : props.title;
const withoutSeo = props.withoutSeo ?? props.withoutMain ?? false;
useFormbricks();
return (
<>
{!withoutSeo && (
<HeadSeo
title={pageTitle ?? APP_NAME}
description={props.description ?? props.subtitle?.toString() ?? ""}
/>
)}
<div>
<Toaster position="bottom-right" />
</div>
@@ -91,8 +81,6 @@ export type LayoutProps = {
flexChildrenContainer?: boolean;
isPublic?: boolean;
withoutMain?: boolean;
// Gives you the option to skip HeadSEO and render your own.
withoutSeo?: boolean;
// Gives the ability to include actions to the right of the heading
actions?: JSX.Element;
beforeCTAactions?: JSX.Element;
-142
View File
@@ -1,142 +0,0 @@
"use client";
import type { NextSeoProps } from "next-seo";
import { NextSeo } from "next-seo";
import { usePathname } from "next/navigation";
import type { AppImageProps, MeetingImageProps } from "@calcom/lib/OgImages";
import { constructAppImage, constructGenericImage, constructMeetingImage } from "@calcom/lib/OgImages";
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
import { buildCanonical, getSeoImage, seoConfig } from "@calcom/lib/next-seo.config";
import { truncateOnWord } from "@calcom/lib/text";
export type HeadSeoProps = {
title: string;
description: string;
siteName?: string;
url?: string;
canonical?: string;
nextSeoProps?: NextSeoProps;
app?: AppImageProps;
meeting?: MeetingImageProps;
isBrandingHidden?: boolean;
origin?: string;
};
/**
* Build full seo tags from title, desc, canonical and url
*/
const buildSeoMeta = (pageProps: {
title: string;
description: string;
image: string;
siteName?: string;
url?: string;
canonical?: string;
}) => {
const { title, description, image, canonical, siteName = seoConfig.headSeo.siteName } = pageProps;
return {
title: title,
canonical: canonical,
openGraph: {
site_name: siteName,
type: "website",
title: title,
description: description,
images: [
{
url: image,
},
],
},
additionalMetaTags: [
{
property: "name",
content: title,
},
{
property: "description",
content: description,
},
{
name: "description",
content: description,
},
{
property: "image",
content: image,
},
],
};
};
export const HeadSeo = (props: HeadSeoProps): JSX.Element => {
const path = usePathname();
// The below code sets the defaultUrl for our canonical tags
// Get the router's path
// Set the default URL to either the current URL (if self-hosted) or https://cal.com canonical URL
const defaultUrl = buildCanonical({ path, origin: props.origin || CAL_URL });
const {
title,
description,
siteName,
canonical = defaultUrl,
nextSeoProps = {},
app,
meeting,
isBrandingHidden,
} = props;
const image = getSeoImage("ogImage") + constructGenericImage({ title, description });
const truncatedDescription = truncateOnWord(description, 158);
const pageTitle = `${title}${isBrandingHidden ? "" : ` | ${APP_NAME}`}`;
let seoObject = buildSeoMeta({
title: pageTitle,
image,
description: truncatedDescription,
canonical,
siteName,
});
if (meeting) {
const pageImage = getSeoImage("ogImage") + constructMeetingImage(meeting);
seoObject = buildSeoMeta({
title: pageTitle,
description: truncatedDescription,
image: pageImage,
canonical,
siteName,
});
}
if (app) {
const pageImage =
getSeoImage("ogImage") + constructAppImage({ ...app, description: truncatedDescription });
seoObject = buildSeoMeta({
title: pageTitle,
description: truncatedDescription,
image: pageImage,
canonical,
siteName,
});
}
// Instead of doing a blackbox deep merge which can be tricky implementation wise and need a good implementation, we should generate the object manually as we know the properties
// Goal is to avoid big dependency
const seoProps: NextSeoProps = {
...nextSeoProps,
...seoObject,
openGraph: {
...nextSeoProps.openGraph,
...seoObject.openGraph,
images: [...(nextSeoProps.openGraph?.images || []), ...seoObject.openGraph.images],
},
additionalMetaTags: [...(nextSeoProps.additionalMetaTags || []), ...seoObject.additionalMetaTags],
};
return <NextSeo {...seoProps} />;
};
export default HeadSeo;
@@ -1,160 +0,0 @@
import { render, waitFor } from "@testing-library/react";
import type { NextSeoProps } from "next-seo";
import type { OpenGraph } from "next/dist/lib/metadata/types/opengraph-types";
import { usePathname } from "next/navigation";
import { vi } from "vitest";
import { CAL_URL } from "@calcom/lib/constants";
import type { HeadSeoProps } from "./HeadSeo";
import HeadSeo from "./HeadSeo";
vi.mock("next/navigation", () => {
return {
usePathname: vi.fn(),
};
});
vi.mock("@calcom/lib/constants", () => {
return {
SEO_IMG_DEFAULT: "",
SEO_IMG_OGIMG: "",
APP_NAME: "Cal.com",
CAL_URL: "http://cal.com",
};
});
vi.mock("next-seo", () => {
return {
NextSeo: (props: NextSeoProps) => {
const { images, ...restOpenGraph } = props.openGraph as OpenGraph & { images: Array<{ url: string }> };
const mockedProps = {
...restOpenGraph,
canonical: props.canonical,
image: images[0].url,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
return <div id="mocked-next-seo" {...mockedProps} />;
},
};
});
vi.mock("@calcom/lib/OgImages", async () => {
return {
constructAppImage() {
return "constructAppImage";
},
constructGenericImage() {
return "constructGenericImage";
},
constructMeetingImage() {
return "constructMeetingImage";
},
};
});
const basicProps = {
image: "image.png",
siteName: "calcomsite",
url: "calcom.com",
canonical: "test canonical",
title: "Test Title",
description: "Test Description",
};
describe("Tests for HeadSeo component", () => {
test("Should render mocked NextSeo", async () => {
const { container } = render(<HeadSeo {...basicProps} />);
await waitFor(async () => {
const mockedNextSeoEl = container.querySelector("#mocked-next-seo");
expect(mockedNextSeoEl?.getAttribute("canonical")).toEqual(basicProps.canonical);
expect(mockedNextSeoEl?.getAttribute("description")).toEqual(basicProps.description);
expect(mockedNextSeoEl?.getAttribute("site_name")).toEqual(basicProps.siteName);
expect(mockedNextSeoEl?.getAttribute("image")).toContain("constructGenericImage");
});
});
describe("Canonical Url", () => {
test("Should provide `canonical` prop to NextSeo derived from `origin` prop if provided", async () => {
const props = {
title: "Test Title",
description: "Test Description",
origin: "http://acme.cal.local",
siteName: "Cal.com",
};
vi.mocked(usePathname).mockReturnValue("/mocked-path");
const { container } = render(<HeadSeo {...props} />);
await waitFor(async () => {
const mockedNextSeoEl = container.querySelector("#mocked-next-seo");
expect(mockedNextSeoEl?.getAttribute("canonical")).toEqual(`${props.origin}/mocked-path`);
expect(mockedNextSeoEl?.getAttribute("description")).toEqual(props.description);
expect(mockedNextSeoEl?.getAttribute("site_name")).toEqual(props.siteName);
expect(mockedNextSeoEl?.getAttribute("image")).toContain("constructGenericImage");
});
});
test("Should provide `canonical` prop to NextSeo derived from `CAL_URL` prop if origin not provided", async () => {
const props = {
title: "Test Title",
description: "Test Description",
siteName: "Cal.com",
};
vi.mocked(usePathname).mockReturnValue("/mocked-path");
const { container } = render(<HeadSeo {...props} />);
await waitFor(async () => {
const mockedNextSeoEl = container.querySelector("#mocked-next-seo");
expect(mockedNextSeoEl?.getAttribute("canonical")).toEqual(`${CAL_URL}/mocked-path`);
expect(mockedNextSeoEl?.getAttribute("description")).toEqual(props.description);
expect(mockedNextSeoEl?.getAttribute("site_name")).toEqual(props.siteName);
expect(mockedNextSeoEl?.getAttribute("image")).toContain("constructGenericImage");
});
});
test("Should provide `canonical` prop to NextSeo from canonical prop if provided", async () => {
const props = {
title: "Test Title",
description: "Test Description",
siteName: "Cal.com",
origin: "http://acme.cal.local",
canonical: "http://acme.cal.local/some-path",
};
vi.mocked(usePathname).mockReturnValue("/mocked-path");
const { container } = render(<HeadSeo {...props} />);
await waitFor(async () => {
const mockedNextSeoEl = container.querySelector("#mocked-next-seo");
expect(mockedNextSeoEl?.getAttribute("canonical")).toEqual(props.canonical);
expect(mockedNextSeoEl?.getAttribute("description")).toEqual(props.description);
expect(mockedNextSeoEl?.getAttribute("site_name")).toEqual(props.siteName);
expect(mockedNextSeoEl?.getAttribute("image")).toContain("constructGenericImage");
});
});
});
test("Should render title with brand", async () => {
const { container } = render(<HeadSeo {...basicProps} />);
await waitFor(async () => {
const mockedNextSeoEl = container.querySelector("#mocked-next-seo");
expect(mockedNextSeoEl?.getAttribute("title")).toEqual(`${basicProps.title} | Cal.com`);
});
});
test("Should render title without brand", async () => {
const { container } = render(<HeadSeo {...basicProps} isBrandingHidden />);
await waitFor(async () => {
const mockedNextSeoEl = container.querySelector("#mocked-next-seo");
expect(mockedNextSeoEl?.getAttribute("title")).toEqual(`${basicProps.title}`);
});
});
test("Should render with app props", async () => {
const { container } = render(<HeadSeo {...basicProps} app={{} as HeadSeoProps["app"]} />);
await waitFor(async () => {
const mockedNextSeoEl = container.querySelector("#mocked-next-seo");
expect(mockedNextSeoEl?.getAttribute("image")).toContain("constructAppImage");
});
});
test("Should render with meeting props", async () => {
const { container } = render(<HeadSeo {...basicProps} meeting={{} as HeadSeoProps["meeting"]} />);
await waitFor(async () => {
const mockedNextSeoEl = container.querySelector("#mocked-next-seo");
expect(mockedNextSeoEl?.getAttribute("image")).toContain("constructMeetingImage");
});
});
});
-1
View File
@@ -1 +0,0 @@
export { HeadSeo } from "./HeadSeo";
-1
View File
@@ -32,7 +32,6 @@
"./components/errorBoundary": "./components/errorBoundary/index.ts",
"./components/form": "./components/form/index.ts",
"./components/form/timezone-select": "./components/form/timezone-select/index.ts",
"./components/head-seo": "./components/head-seo/index.ts",
"./components/hover-card": "./components/hover-card/index.tsx",
"./components/image-uploader": "./components/image-uploader/index.ts",
"./components/layout": "./components/layout/index.ts",