diff --git a/.env.example b/.env.example
index a3b4d7b290..20d9aba8f1 100644
--- a/.env.example
+++ b/.env.example
@@ -357,7 +357,6 @@ E2E_TEST_OIDC_USER_PASSWORD=
# redirected from the legacy to the future pages
AB_TEST_BUCKET_PROBABILITY=50
APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED=0
-APP_ROUTER_APPS_SLUG_ENABLED=0
APP_ROUTER_APPS_SLUG_SETUP_ENABLED=0
# whether we redirect to the future/apps/categories from /apps/categories or not
APP_ROUTER_APPS_CATEGORIES_ENABLED=0
diff --git a/apps/web/abTest/middlewareFactory.ts b/apps/web/abTest/middlewareFactory.ts
index 2c0d74bc86..59e53e43d5 100644
--- a/apps/web/abTest/middlewareFactory.ts
+++ b/apps/web/abTest/middlewareFactory.ts
@@ -6,7 +6,6 @@ import { FUTURE_ROUTES_ENABLED_COOKIE_NAME, FUTURE_ROUTES_OVERRIDE_COOKIE_NAME }
const ROUTES: [URLPattern, boolean][] = [
["/apps/installed/:category", process.env.APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED === "1"] as const,
- ["/apps/:slug", process.env.APP_ROUTER_APPS_SLUG_ENABLED === "1"] as const,
["/apps/:slug/setup", process.env.APP_ROUTER_APPS_SLUG_SETUP_ENABLED === "1"] as const,
["/apps/categories", process.env.APP_ROUTER_APPS_CATEGORIES_ENABLED === "1"] as const,
["/apps/categories/:category", process.env.APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED === "1"] as const,
diff --git a/apps/web/app/(use-page-wrapper)/apps/[slug]/page.tsx b/apps/web/app/(use-page-wrapper)/apps/[slug]/page.tsx
new file mode 100644
index 0000000000..94b15fd498
--- /dev/null
+++ b/apps/web/app/(use-page-wrapper)/apps/[slug]/page.tsx
@@ -0,0 +1,51 @@
+import type { PageProps as _PageProps } from "app/_types";
+import { generateAppMetadata } from "app/_utils";
+import { notFound } from "next/navigation";
+import { z } from "zod";
+
+import { getStaticProps } from "@lib/apps/[slug]/getStaticProps";
+
+import AppView from "~/apps/[slug]/slug-view";
+
+const paramsSchema = z.object({
+ slug: z.string(),
+});
+
+export const generateMetadata = async ({ params }: _PageProps) => {
+ const p = paramsSchema.safeParse(params);
+
+ if (!p.success) {
+ return notFound();
+ }
+
+ const props = await getStaticProps(p.data.slug);
+
+ if (!props) {
+ notFound();
+ }
+ const { name, logo, description } = props.data;
+
+ return await generateAppMetadata(
+ { slug: logo, name, description },
+ () => name,
+ () => description
+ );
+};
+
+async function Page({ params }: _PageProps) {
+ const p = paramsSchema.safeParse(params);
+
+ if (!p.success) {
+ return notFound();
+ }
+
+ const props = await getStaticProps(p.data.slug);
+
+ if (!props) {
+ notFound();
+ }
+
+ return ;
+}
+
+export default Page;
diff --git a/apps/web/app/_utils.tsx b/apps/web/app/_utils.tsx
index dd05e0d3ae..acda1dd520 100644
--- a/apps/web/app/_utils.tsx
+++ b/apps/web/app/_utils.tsx
@@ -3,8 +3,8 @@ 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 type { AppImageProps, MeetingImageProps } from "@calcom/lib/OgImages";
+import { constructAppImage, 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";
@@ -123,3 +123,23 @@ export const generateMeetingMetadata = async (
},
};
};
+
+export const generateAppMetadata = async (
+ app: AppImageProps,
+ getTitle: (t: TFunction) => string,
+ getDescription: (t: TFunction) => string,
+ hideBranding?: boolean,
+ origin?: string
+) => {
+ const metadata = await _generateMetadataWithoutImage(getTitle, getDescription, hideBranding, origin);
+
+ const image = SEO_IMG_OGIMG + constructAppImage({ ...app, description: metadata.description });
+
+ return {
+ ...metadata,
+ openGraph: {
+ ...metadata.openGraph,
+ images: [image],
+ },
+ };
+};
diff --git a/apps/web/app/future/apps/[slug]/page.tsx b/apps/web/app/future/apps/[slug]/page.tsx
deleted file mode 100644
index 3c7f717544..0000000000
--- a/apps/web/app/future/apps/[slug]/page.tsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import { Prisma } from "@prisma/client";
-import { withAppDirSsg } from "app/WithAppDirSsg";
-import type { PageProps as _PageProps } from "app/_types";
-import { _generateMetadata } from "app/_utils";
-import { WithLayout } from "app/layoutHOC";
-import { cookies, headers } from "next/headers";
-
-import { AppRepository } from "@calcom/lib/server/repository/app";
-
-import { getStaticProps } from "@lib/apps/[slug]/getStaticProps";
-import { buildLegacyCtx } from "@lib/buildLegacyCtx";
-
-import type { PageProps } from "~/apps/[slug]/slug-view";
-import Page from "~/apps/[slug]/slug-view";
-
-const getData = withAppDirSsg(getStaticProps, "future/apps/[slug]");
-
-export const generateMetadata = async ({ params, searchParams }: _PageProps) => {
- const legacyContext = buildLegacyCtx(headers(), cookies(), params, searchParams);
- const res = await getData(legacyContext);
-
- return await _generateMetadata(
- () => res?.data.name ?? "",
- () => res?.data.description ?? ""
- );
-};
-
-export const generateStaticParams = async () => {
- try {
- const appStore = await AppRepository.findAppStore();
- return appStore.map(({ slug }) => ({ slug }));
- } catch (e: unknown) {
- if (e instanceof Prisma.PrismaClientInitializationError) {
- // Database is not available at build time, but that's ok – we fall back to resolving paths on demand
- } else {
- throw e;
- }
- }
-
- return [];
-};
-
-export default WithLayout({ getLayout: null, Page, getData });
-
-export const dynamic = "force-static";
diff --git a/apps/web/lib/apps/[slug]/getStaticProps.tsx b/apps/web/lib/apps/[slug]/getStaticProps.tsx
index 9113ba9c4e..61079a2834 100644
--- a/apps/web/lib/apps/[slug]/getStaticProps.tsx
+++ b/apps/web/lib/apps/[slug]/getStaticProps.tsx
@@ -1,7 +1,5 @@
import fs from "fs";
import matter from "gray-matter";
-import MarkdownIt from "markdown-it";
-import type { GetStaticPropsContext } from "next";
import path from "path";
import { z } from "zod";
@@ -10,8 +8,6 @@ import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
-const md = new MarkdownIt("default", { html: true, breaks: true });
-
export const sourceSchema = z.object({
content: z.string(),
data: z.object({
@@ -29,15 +25,15 @@ export const sourceSchema = z.object({
}),
});
-export const getStaticProps = async (ctx: GetStaticPropsContext) => {
- if (typeof ctx.params?.slug !== "string") return { notFound: true } as const;
+export type AppDataProps = NonNullable>>;
+export const getStaticProps = async (slug: string) => {
const appMeta = await getAppWithMetadata({
- slug: ctx.params?.slug,
+ slug,
});
const appFromDb = await prisma.app.findUnique({
- where: { slug: ctx.params.slug.toLowerCase() },
+ where: { slug: slug.toLowerCase() },
});
const isAppAvailableInFileSystem = appMeta;
@@ -45,16 +41,14 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
if (!IS_PRODUCTION && isAppDisabled) {
return {
- props: {
- isAppDisabled: true as const,
- data: {
- ...appMeta,
- },
+ isAppDisabled: true as const,
+ data: {
+ ...appMeta,
},
};
}
- if (!appFromDb || !appMeta || isAppDisabled) return { notFound: true } as const;
+ if (!appFromDb || !appMeta || isAppDisabled) return null;
const isTemplate = appMeta.isTemplate;
const appDirname = path.join(isTemplate ? "templates" : "", appFromDb.dirName);
@@ -85,10 +79,8 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
});
}
return {
- props: {
- isAppDisabled: false as const,
- source: { content, data },
- data: appMeta,
- },
+ isAppDisabled: false as const,
+ source: { content, data },
+ data: appMeta,
};
};
diff --git a/apps/web/modules/apps/[slug]/slug-view.tsx b/apps/web/modules/apps/[slug]/slug-view.tsx
index 6331f81e40..b0faacc283 100644
--- a/apps/web/modules/apps/[slug]/slug-view.tsx
+++ b/apps/web/modules/apps/[slug]/slug-view.tsx
@@ -1,7 +1,5 @@
"use client";
-import MarkdownIt from "markdown-it";
-import type { InferGetStaticPropsType } from "next";
import Link from "next/link";
import { IS_PRODUCTION } from "@calcom/lib/constants";
@@ -9,16 +7,12 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { showToast } from "@calcom/ui";
-import type { getStaticProps } from "@lib/apps/[slug]/getStaticProps";
+import type { AppDataProps } from "@lib/apps/[slug]/getStaticProps";
import useRouterQuery from "@lib/hooks/useRouterQuery";
import App from "@components/apps/App";
-const md = new MarkdownIt("default", { html: true, breaks: true });
-
-export type PageProps = InferGetStaticPropsType;
-
-function SingleAppPage(props: PageProps) {
+function SingleAppPage(props: AppDataProps) {
const { error, setQuery: setError } = useRouterQuery("error");
const { t } = useLocale();
if (error === "account_already_linked") {
diff --git a/apps/web/pages/apps/[slug]/index.tsx b/apps/web/pages/apps/[slug]/index.tsx
deleted file mode 100644
index b3419ceb9b..0000000000
--- a/apps/web/pages/apps/[slug]/index.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import { Prisma } from "@prisma/client";
-import type { GetStaticPaths } from "next";
-
-import { AppRepository } from "@calcom/lib/server/repository/app";
-
-import { getStaticProps } from "@lib/apps/[slug]/getStaticProps";
-
-import PageWrapper from "@components/PageWrapper";
-
-import type { PageProps } from "~/apps/[slug]/slug-view";
-import SingleAppPage from "~/apps/[slug]/slug-view";
-
-const Page = (props: PageProps) => ;
-
-export const getStaticPaths: GetStaticPaths<{ slug: string }> = async () => {
- let paths: { params: { slug: string } }[] = [];
-
- try {
- const appStore = await AppRepository.findAppStore();
- paths = appStore.map(({ slug }) => ({ params: { slug } }));
- } catch (e: unknown) {
- if (e instanceof Prisma.PrismaClientInitializationError) {
- // Database is not available at build time, but that's ok – we fall back to resolving paths on demand
- } else {
- throw e;
- }
- }
-
- return {
- paths,
- fallback: "blocking",
- };
-};
-
-export { getStaticProps };
-
-Page.PageWrapper = PageWrapper;
-
-export default Page;
diff --git a/apps/web/scripts/vercel-app-router-deploy.sh b/apps/web/scripts/vercel-app-router-deploy.sh
index 6600bf0780..444c8aae1b 100755
--- a/apps/web/scripts/vercel-app-router-deploy.sh
+++ b/apps/web/scripts/vercel-app-router-deploy.sh
@@ -7,7 +7,6 @@ checkRoute () {
# These conditionals are used to remove directories from the build that are not needed in production
# This is to reduce the size of the build and prevent OOM errors
checkRoute "$APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED" app/future/apps/installed
-checkRoute "$APP_ROUTER_APPS_SLUG_ENABLED" app/future/apps/\[slug\]
checkRoute "$APP_ROUTER_APPS_SLUG_SETUP_ENABLED" app/future/apps/\[slug\]/setup
checkRoute "$APP_ROUTER_APPS_CATEGORIES_ENABLED" app/future/apps/categories
checkRoute "$APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED" app/future/apps/categories/\[category\]
diff --git a/turbo.json b/turbo.json
index a511a9b179..b0880ec5c4 100644
--- a/turbo.json
+++ b/turbo.json
@@ -243,7 +243,6 @@
"APP_ROUTER_APPS_CATEGORIES_CATEGORY_ENABLED",
"APP_ROUTER_APPS_CATEGORIES_ENABLED",
"APP_ROUTER_APPS_INSTALLED_CATEGORY_ENABLED",
- "APP_ROUTER_APPS_SLUG_ENABLED",
"APP_ROUTER_APPS_SLUG_SETUP_ENABLED",
"APP_ROUTER_AUTH_FORGOT_PASSWORD_ENABLED",
"APP_ROUTER_AUTH_LOGIN_ENABLED",