From 8ed2aacd9e1b106a2ac1deb95d93aa17f6c38275 Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Wed, 20 Aug 2025 17:21:55 +0530 Subject: [PATCH] Add organization rewrite logs (#23210) --- apps/web/next.config.js | 968 ++++++++++++++++++++-------------------- 1 file changed, 490 insertions(+), 478 deletions(-) diff --git a/apps/web/next.config.js b/apps/web/next.config.js index 7522f12ac6..cf5b03617d 100644 --- a/apps/web/next.config.js +++ b/apps/web/next.config.js @@ -170,513 +170,525 @@ const orgDomainMatcherConfig = { }; /** @type {import("next").NextConfig} */ -const nextConfig = { - output: process.env.BUILD_STANDALONE === "true" ? "standalone" : undefined, - serverExternalPackages: [ - "deasync", - "http-cookie-agent", // Dependencies of @ewsjs/xhr - "rest-facade", - "superagent-proxy", // Dependencies of @tryvital/vital-node - "superagent", // Dependencies of akismet - "formidable", // Dependencies of akismet - "@boxyhq/saml-jackson", - "jose", // Dependency of @boxyhq/saml-jackson - ], - experimental: { - // externalize server-side node_modules with size > 1mb, to improve dev mode performance/RAM usage - optimizePackageImports: ["@calcom/ui"], - }, - productionBrowserSourceMaps: true, - /* We already do type check on GH actions */ - typescript: { - ignoreBuildErrors: !!process.env.CI, - }, - /* We already do linting on GH actions */ - eslint: { - ignoreDuringBuilds: !!process.env.CI, - }, - transpilePackages: [ - "@calcom/app-store", - "@calcom/dayjs", - "@calcom/emails", - "@calcom/embed-core", - "@calcom/embed-react", - "@calcom/embed-snippet", - "@calcom/features", - "@calcom/lib", - "@calcom/prisma", - "@calcom/trpc", - ], - modularizeImports: { - "@calcom/features/insights/components": { - transform: "@calcom/features/insights/components/{{member}}", - skipDefaultConversion: true, - preventFullImport: true, +const nextConfig = (phase) => { + if (isOrganizationsEnabled) { + // We want to log the phase here because it is important that the rewrite is added during the build phase(phase=phase-production-build) + console.log( + `[Phase: ${phase}] Adding rewrite config for organizations - orgHostPath: ${nextJsOrgRewriteConfig.orgHostPath}, orgSlug: ${nextJsOrgRewriteConfig.orgSlug}, disableRootPathRewrite: ${nextJsOrgRewriteConfig.disableRootPathRewrite}` + ); + } else { + console.log( + `[Phase: ${phase}] Skipping rewrite config for organizations because ORGANIZATIONS_ENABLED is not set` + ); + } + return { + output: process.env.BUILD_STANDALONE === "true" ? "standalone" : undefined, + serverExternalPackages: [ + "deasync", + "http-cookie-agent", // Dependencies of @ewsjs/xhr + "rest-facade", + "superagent-proxy", // Dependencies of @tryvital/vital-node + "superagent", // Dependencies of akismet + "formidable", // Dependencies of akismet + "@boxyhq/saml-jackson", + "jose", // Dependency of @boxyhq/saml-jackson + ], + experimental: { + // externalize server-side node_modules with size > 1mb, to improve dev mode performance/RAM usage + optimizePackageImports: ["@calcom/ui"], }, - lodash: { - transform: "lodash/{{member}}", + productionBrowserSourceMaps: true, + /* We already do type check on GH actions */ + typescript: { + ignoreBuildErrors: !!process.env.CI, }, - }, - images: { - unoptimized: true, - }, - webpack: (config, { webpack, buildId, isServer }) => { - if (isServer) { - // Module not found fix @see https://github.com/boxyhq/jackson/issues/1535#issuecomment-1704381612 - config.plugins.push( - new webpack.IgnorePlugin({ - resourceRegExp: - /(^@google-cloud\/spanner|^@mongodb-js\/zstd|^@sap\/hana-client\/extension\/Stream$|^@sap\/hana-client|^@sap\/hana-client$|^aws-crt|^aws4$|^better-sqlite3$|^bson-ext$|^cardinal$|^cloudflare:sockets$|^hdb-pool$|^ioredis$|^kerberos$|^mongodb-client-encryption$|^mysql$|^oracledb$|^pg-native$|^pg-query-stream$|^react-native-sqlite-storage$|^snappy\/package\.json$|^snappy$|^sql.js$|^sqlite3$|^typeorm-aurora-data-api-driver$)/, - }) - ); - - config.externals.push("formidable"); - } - - config.plugins.push(new webpack.DefinePlugin({ "process.env.BUILD_ID": JSON.stringify(buildId) })); - - config.resolve.fallback = { - ...config.resolve.fallback, // if you miss it, all the other options in fallback, specified - // by next.js will be dropped. Doesn't make much sense, but how it is - fs: false, - // ignore module resolve errors caused by the server component bundler - "pg-native": false, - }; - - /** - * TODO: Find more possible barrels for this project. - * @see https://github.com/vercel/next.js/issues/12557#issuecomment-1196931845 - **/ - config.module.rules.push({ - test: [/lib\/.*.tsx?/i], - sideEffects: false, - }); - - return config; - }, - async rewrites() { - const { orgSlug } = nextJsOrgRewriteConfig; - const beforeFiles = [ - { - // This should be the first item in `beforeFiles` to take precedence over other rewrites - source: `/(${locales.join("|")})/:path*`, - destination: "/:path*", + /* We already do linting on GH actions */ + eslint: { + ignoreDuringBuilds: !!process.env.CI, + }, + transpilePackages: [ + "@calcom/app-store", + "@calcom/dayjs", + "@calcom/emails", + "@calcom/embed-core", + "@calcom/embed-react", + "@calcom/embed-snippet", + "@calcom/features", + "@calcom/lib", + "@calcom/prisma", + "@calcom/trpc", + ], + modularizeImports: { + "@calcom/features/insights/components": { + transform: "@calcom/features/insights/components/{{member}}", + skipDefaultConversion: true, + preventFullImport: true, }, - { - source: "/forms/:formQuery*", - destination: "/apps/routing-forms/routing-link/:formQuery*", + lodash: { + transform: "lodash/{{member}}", }, - { - source: "/routing", - destination: "/routing/forms", - }, - { - source: "/routing/:path*", - destination: "/apps/routing-forms/:path*", - }, - { - source: "/success/:path*", - has: [ + }, + images: { + unoptimized: true, + }, + webpack: (config, { webpack, buildId, isServer }) => { + if (isServer) { + // Module not found fix @see https://github.com/boxyhq/jackson/issues/1535#issuecomment-1704381612 + config.plugins.push( + new webpack.IgnorePlugin({ + resourceRegExp: + /(^@google-cloud\/spanner|^@mongodb-js\/zstd|^@sap\/hana-client\/extension\/Stream$|^@sap\/hana-client|^@sap\/hana-client$|^aws-crt|^aws4$|^better-sqlite3$|^bson-ext$|^cardinal$|^cloudflare:sockets$|^hdb-pool$|^ioredis$|^kerberos$|^mongodb-client-encryption$|^mysql$|^oracledb$|^pg-native$|^pg-query-stream$|^react-native-sqlite-storage$|^snappy\/package\.json$|^snappy$|^sql.js$|^sqlite3$|^typeorm-aurora-data-api-driver$)/, + }) + ); + + config.externals.push("formidable"); + } + + config.plugins.push(new webpack.DefinePlugin({ "process.env.BUILD_ID": JSON.stringify(buildId) })); + + config.resolve.fallback = { + ...config.resolve.fallback, // if you miss it, all the other options in fallback, specified + // by next.js will be dropped. Doesn't make much sense, but how it is + fs: false, + // ignore module resolve errors caused by the server component bundler + "pg-native": false, + }; + + /** + * TODO: Find more possible barrels for this project. + * @see https://github.com/vercel/next.js/issues/12557#issuecomment-1196931845 + **/ + config.module.rules.push({ + test: [/lib\/.*.tsx?/i], + sideEffects: false, + }); + + return config; + }, + async rewrites() { + const { orgSlug } = nextJsOrgRewriteConfig; + const beforeFiles = [ + { + // This should be the first item in `beforeFiles` to take precedence over other rewrites + source: `/(${locales.join("|")})/:path*`, + destination: "/:path*", + }, + { + source: "/forms/:formQuery*", + destination: "/apps/routing-forms/routing-link/:formQuery*", + }, + { + source: "/routing", + destination: "/routing/forms", + }, + { + source: "/routing/:path*", + destination: "/apps/routing-forms/:path*", + }, + { + source: "/success/:path*", + has: [ + { + type: "query", + key: "uid", + value: "(?.*)", + }, + ], + destination: "/booking/:uid/:path*", + }, + { + source: "/cancel/:path*", + destination: "/booking/:path*", + }, + { + /** + * Needed due to the introduction of dotted usernames + * @see https://github.com/calcom/cal.com/pull/11706 + */ + source: "/embed.js", + destination: "/embed/embed.js", + }, + { + source: "/login", + destination: "/auth/login", + }, + // These rewrites are other than booking pages rewrites and so that they aren't redirected to org pages ensure that they happen in beforeFiles + ...(isOrganizationsEnabled + ? [ + orgDomainMatcherConfig.root + ? { + ...orgDomainMatcherConfig.root, + destination: `/team/${orgSlug}?isOrgProfile=1`, + } + : null, + orgDomainMatcherConfig.rootEmbed + ? { + ...orgDomainMatcherConfig.rootEmbed, + destination: `/team/${orgSlug}/embed?isOrgProfile=1`, + } + : null, + { + ...orgDomainMatcherConfig.user, + destination: `/org/${orgSlug}/:user`, + }, + { + ...orgDomainMatcherConfig.userType, + destination: `/org/${orgSlug}/:user/:type`, + }, + { + ...orgDomainMatcherConfig.userTypeEmbed, + destination: `/org/${orgSlug}/:user/:type/embed`, + }, + ] + : []), + ].filter(Boolean); + + let afterFiles = [ + { + source: "/org/:slug", + destination: "/team/:slug", + }, + { + source: "/org/:orgSlug/avatar.png", + destination: "/api/user/avatar?orgSlug=:orgSlug", + }, + { + source: "/team/:teamname/avatar.png", + destination: "/api/user/avatar?teamname=:teamname", + }, + { + source: "/icons/sprite.svg", + destination: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/icons/sprite.svg`, + }, + // for @dub/analytics, @see: https://d.to/reverse-proxy + { + source: "/_proxy/dub/track/:path", + destination: "https://api.dub.co/track/:path", + }, + + // When updating this also update pagesAndRewritePaths.js + ...[ { - type: "query", - key: "uid", - value: "(?.*)", + source: "/:user/avatar.png", + destination: "/api/user/avatar?username=:user", }, ], - destination: "/booking/:uid/:path*", - }, - { - source: "/cancel/:path*", - destination: "/booking/:path*", - }, - { - /** - * Needed due to the introduction of dotted usernames - * @see https://github.com/calcom/cal.com/pull/11706 - */ - source: "/embed.js", - destination: "/embed/embed.js", - }, - { - source: "/login", - destination: "/auth/login", - }, - // These rewrites are other than booking pages rewrites and so that they aren't redirected to org pages ensure that they happen in beforeFiles - ...(isOrganizationsEnabled - ? [ - orgDomainMatcherConfig.root - ? { - ...orgDomainMatcherConfig.root, - destination: `/team/${orgSlug}?isOrgProfile=1`, - } - : null, - orgDomainMatcherConfig.rootEmbed - ? { - ...orgDomainMatcherConfig.rootEmbed, - destination: `/team/${orgSlug}/embed?isOrgProfile=1`, - } - : null, - { - ...orgDomainMatcherConfig.user, - destination: `/org/${orgSlug}/:user`, - }, - { - ...orgDomainMatcherConfig.userType, - destination: `/org/${orgSlug}/:user/:type`, - }, - { - ...orgDomainMatcherConfig.userTypeEmbed, - destination: `/org/${orgSlug}/:user/:type/embed`, - }, - ] - : []), - ].filter(Boolean); - let afterFiles = [ - { - source: "/org/:slug", - destination: "/team/:slug", - }, - { - source: "/org/:orgSlug/avatar.png", - destination: "/api/user/avatar?orgSlug=:orgSlug", - }, - { - source: "/team/:teamname/avatar.png", - destination: "/api/user/avatar?teamname=:teamname", - }, - { - source: "/icons/sprite.svg", - destination: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/icons/sprite.svg`, - }, - // for @dub/analytics, @see: https://d.to/reverse-proxy - { - source: "/_proxy/dub/track/:path", - destination: "https://api.dub.co/track/:path", - }, - - // When updating this also update pagesAndRewritePaths.js - ...[ - { - source: "/:user/avatar.png", - destination: "/api/user/avatar?username=:user", - }, - ], - - /* TODO: have these files being served from another deployment or CDN { + /* TODO: have these files being served from another deployment or CDN { source: "/embed/embed.js", destination: process.env.NEXT_PUBLIC_EMBED_LIB_URL?, }, */ - ]; + ]; - if (Boolean(process.env.NEXT_PUBLIC_API_V2_URL)) { - afterFiles.push({ - source: "/api/v2/:path*", - destination: `${process.env.NEXT_PUBLIC_API_V2_URL}/:path*`, - }); - } + if (Boolean(process.env.NEXT_PUBLIC_API_V2_URL)) { + afterFiles.push({ + source: "/api/v2/:path*", + destination: `${process.env.NEXT_PUBLIC_API_V2_URL}/:path*`, + }); + } - return { - beforeFiles, - afterFiles, - }; - }, - async headers() { - const { orgSlug } = nextJsOrgRewriteConfig; - // This header can be set safely as it ensures the browser will load the resources even when COEP is set. - // But this header must be set only on those resources that are safe to be loaded in a cross-origin context e.g. all embeddable pages's resources - const CORP_CROSS_ORIGIN_HEADER = { - key: "Cross-Origin-Resource-Policy", - value: "cross-origin", - }; + return { + beforeFiles, + afterFiles, + }; + }, + async headers() { + const { orgSlug } = nextJsOrgRewriteConfig; + // This header can be set safely as it ensures the browser will load the resources even when COEP is set. + // But this header must be set only on those resources that are safe to be loaded in a cross-origin context e.g. all embeddable pages's resources + const CORP_CROSS_ORIGIN_HEADER = { + key: "Cross-Origin-Resource-Policy", + value: "cross-origin", + }; - const ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = { - key: "Access-Control-Allow-Origin", - value: "*", - }; + const ACCESS_CONTROL_ALLOW_ORIGIN_HEADER = { + key: "Access-Control-Allow-Origin", + value: "*", + }; - return [ - { - source: "/auth/:path*", - headers: [ - { - key: "X-Frame-Options", - value: "DENY", - }, - ], - }, - { - source: "/signup", - headers: [ - { - key: "X-Frame-Options", - value: "DENY", - }, - ], - }, - { - source: "/:path*", - headers: [ - { - key: "X-Content-Type-Options", - value: "nosniff", - }, - { - key: "Referrer-Policy", - value: "strict-origin-when-cross-origin", - }, - ], - }, - { - source: "/embed/embed.js", - headers: [CORP_CROSS_ORIGIN_HEADER], - }, - { - source: "/:path*/embed", - // COEP require-corp header is set conditionally when flag.coep is set to true - headers: [CORP_CROSS_ORIGIN_HEADER], - }, - { - source: "/:path*", - has: [ - { - type: "host", - value: "cal.com", - }, - ], - headers: [ - // make sure to pass full referer URL for booking pages - { - key: "Referrer-Policy", - value: "no-referrer-when-downgrade", - }, - ], - }, - // These resources loads through embed as well, so they need to have CORP_CROSS_ORIGIN_HEADER - ...[ + return [ { - source: "/api/avatar/:path*", - headers: [CORP_CROSS_ORIGIN_HEADER], - }, - { - source: "/avatar.svg", - headers: [CORP_CROSS_ORIGIN_HEADER], - }, - { - source: "/icons/sprite.svg(\\?v=[0-9a-zA-Z\\-\\.]+)?", + source: "/auth/:path*", headers: [ - CORP_CROSS_ORIGIN_HEADER, - ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, { - key: "Cache-Control", - value: "public, max-age=31536000, immutable", + key: "X-Frame-Options", + value: "DENY", }, ], }, - ], - ...(isOrganizationsEnabled - ? [ - orgDomainMatcherConfig.root - ? { - ...orgDomainMatcherConfig.root, - headers: [ - { - key: "X-Cal-Org-path", - value: `/team/${orgSlug}`, - }, - ], - } - : null, - { - ...orgDomainMatcherConfig.user, - headers: [ - { - key: "X-Cal-Org-path", - value: `/org/${orgSlug}/:user`, - }, - ], - }, - { - ...orgDomainMatcherConfig.userType, - headers: [ - { - key: "X-Cal-Org-path", - value: `/org/${orgSlug}/:user/:type`, - }, - ], - }, - { - ...orgDomainMatcherConfig.userTypeEmbed, - headers: [ - { - key: "X-Cal-Org-path", - value: `/org/${orgSlug}/:user/:type/embed`, - }, - ], - }, - ] - : []), - ].filter(Boolean); - }, - async redirects() { - const redirects = [ - { - source: "/settings/organizations", - destination: "/settings/organizations/profile", - permanent: false, - }, - { - source: "/apps/routing-forms", - destination: "/routing/forms", - permanent: false, - }, - { - source: "/api/app-store/:path*", - destination: "/app-store/:path*", - permanent: true, - }, - { - source: "/auth/signup", - destination: "/signup", - permanent: true, - }, - { - source: "/auth", - destination: "/auth/login", - permanent: false, - }, - { - source: "/settings", - destination: "/settings/my-account/profile", - permanent: true, - }, - { - source: "/settings/teams", - destination: "/teams", - permanent: true, - }, - { - source: "/settings/admin", - destination: "/settings/admin/flags", - permanent: true, - }, - /* V2 testers get redirected to the new settings */ - { - source: "/settings/profile", - destination: "/settings/my-account/profile", - permanent: false, - }, - { - source: "/settings/security", - destination: "/settings/security/password", - permanent: false, - }, - { - source: "/bookings", - destination: "/bookings/upcoming", - permanent: true, - }, - { - source: "/call/:path*", - destination: "/video/:path*", - permanent: false, - }, - /* Attempt to mitigate DDoS attack */ - { - source: "/api/auth/:path*", - has: [ - { - type: "query", - key: "callbackUrl", - // prettier-ignore - value: "^(?!https?:\/\/).*$", - }, - ], - destination: "/404", - permanent: false, - }, - { - source: "/booking/direct/:action/:email/:bookingUid/:oldToken", - destination: "/api/link?action=:action&email=:email&bookingUid=:bookingUid&oldToken=:oldToken", - permanent: true, - }, - { - source: "/support", - missing: [ - { - type: "header", - key: "host", - value: nextJsOrgRewriteConfig.orgHostPath, - }, - ], - destination: "/event-types?openPlain=true", - permanent: true, - }, - { - source: "/apps/categories/video", - destination: "/apps/categories/conferencing", - permanent: true, - }, - { - source: "/apps/installed/video", - destination: "/apps/installed/conferencing", - permanent: true, - }, - { - source: "/apps/installed", - destination: "/apps/installed/calendar", - permanent: true, - }, - { - source: "/settings/organizations/platform/:path*", - destination: "/settings/platform", - permanent: true, - }, - // OAuth callbacks when sent to localhost:3000(w would be expected) should be redirected to corresponding to WEBAPP_URL - ...(process.env.NODE_ENV === "development" && - // Safer to enable the redirect only when the user is opting to test out organizations - isOrganizationsEnabled && - // Prevent infinite redirect by checking that we aren't already on localhost - process.env.NEXT_PUBLIC_WEBAPP_URL !== "http://localhost:3000" - ? [ - { - has: [ - { - type: "header", - key: "host", - value: "localhost:3000", - }, - ], - source: "/api/integrations/:args*", - destination: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/integrations/:args*`, - permanent: false, - }, - ] - : []), - ]; - - if (process.env.NEXT_PUBLIC_WEBAPP_URL === "https://app.cal.com") { - redirects.push( { - source: "/apps/dailyvideo", - destination: "/apps/daily-video", + source: "/signup", + headers: [ + { + key: "X-Frame-Options", + value: "DENY", + }, + ], + }, + { + source: "/:path*", + headers: [ + { + key: "X-Content-Type-Options", + value: "nosniff", + }, + { + key: "Referrer-Policy", + value: "strict-origin-when-cross-origin", + }, + ], + }, + { + source: "/embed/embed.js", + headers: [CORP_CROSS_ORIGIN_HEADER], + }, + { + source: "/:path*/embed", + // COEP require-corp header is set conditionally when flag.coep is set to true + headers: [CORP_CROSS_ORIGIN_HEADER], + }, + { + source: "/:path*", + has: [ + { + type: "host", + value: "cal.com", + }, + ], + headers: [ + // make sure to pass full referer URL for booking pages + { + key: "Referrer-Policy", + value: "no-referrer-when-downgrade", + }, + ], + }, + // These resources loads through embed as well, so they need to have CORP_CROSS_ORIGIN_HEADER + ...[ + { + source: "/api/avatar/:path*", + headers: [CORP_CROSS_ORIGIN_HEADER], + }, + { + source: "/avatar.svg", + headers: [CORP_CROSS_ORIGIN_HEADER], + }, + { + source: "/icons/sprite.svg(\\?v=[0-9a-zA-Z\\-\\.]+)?", + headers: [ + CORP_CROSS_ORIGIN_HEADER, + ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, + { + key: "Cache-Control", + value: "public, max-age=31536000, immutable", + }, + ], + }, + ], + ...(isOrganizationsEnabled + ? [ + orgDomainMatcherConfig.root + ? { + ...orgDomainMatcherConfig.root, + headers: [ + { + key: "X-Cal-Org-path", + value: `/team/${orgSlug}`, + }, + ], + } + : null, + { + ...orgDomainMatcherConfig.user, + headers: [ + { + key: "X-Cal-Org-path", + value: `/org/${orgSlug}/:user`, + }, + ], + }, + { + ...orgDomainMatcherConfig.userType, + headers: [ + { + key: "X-Cal-Org-path", + value: `/org/${orgSlug}/:user/:type`, + }, + ], + }, + { + ...orgDomainMatcherConfig.userTypeEmbed, + headers: [ + { + key: "X-Cal-Org-path", + value: `/org/${orgSlug}/:user/:type/embed`, + }, + ], + }, + ] + : []), + ].filter(Boolean); + }, + async redirects() { + const redirects = [ + { + source: "/settings/organizations", + destination: "/settings/organizations/profile", + permanent: false, + }, + { + source: "/apps/routing-forms", + destination: "/routing/forms", + permanent: false, + }, + { + source: "/api/app-store/:path*", + destination: "/app-store/:path*", permanent: true, }, { - source: "/apps/huddle01_video", - destination: "/apps/huddle01", + source: "/auth/signup", + destination: "/signup", permanent: true, }, { - source: "/apps/jitsi_video", - destination: "/apps/jitsi", + source: "/auth", + destination: "/auth/login", + permanent: false, + }, + { + source: "/settings", + destination: "/settings/my-account/profile", permanent: true, - } - ); - } + }, + { + source: "/settings/teams", + destination: "/teams", + permanent: true, + }, + { + source: "/settings/admin", + destination: "/settings/admin/flags", + permanent: true, + }, + /* V2 testers get redirected to the new settings */ + { + source: "/settings/profile", + destination: "/settings/my-account/profile", + permanent: false, + }, + { + source: "/settings/security", + destination: "/settings/security/password", + permanent: false, + }, + { + source: "/bookings", + destination: "/bookings/upcoming", + permanent: true, + }, + { + source: "/call/:path*", + destination: "/video/:path*", + permanent: false, + }, + /* Attempt to mitigate DDoS attack */ + { + source: "/api/auth/:path*", + has: [ + { + type: "query", + key: "callbackUrl", + // prettier-ignore + value: "^(?!https?:\/\/).*$", + }, + ], + destination: "/404", + permanent: false, + }, + { + source: "/booking/direct/:action/:email/:bookingUid/:oldToken", + destination: "/api/link?action=:action&email=:email&bookingUid=:bookingUid&oldToken=:oldToken", + permanent: true, + }, + { + source: "/support", + missing: [ + { + type: "header", + key: "host", + value: nextJsOrgRewriteConfig.orgHostPath, + }, + ], + destination: "/event-types?openPlain=true", + permanent: true, + }, + { + source: "/apps/categories/video", + destination: "/apps/categories/conferencing", + permanent: true, + }, + { + source: "/apps/installed/video", + destination: "/apps/installed/conferencing", + permanent: true, + }, + { + source: "/apps/installed", + destination: "/apps/installed/calendar", + permanent: true, + }, + { + source: "/settings/organizations/platform/:path*", + destination: "/settings/platform", + permanent: true, + }, + // OAuth callbacks when sent to localhost:3000(w would be expected) should be redirected to corresponding to WEBAPP_URL + ...(process.env.NODE_ENV === "development" && + // Safer to enable the redirect only when the user is opting to test out organizations + isOrganizationsEnabled && + // Prevent infinite redirect by checking that we aren't already on localhost + process.env.NEXT_PUBLIC_WEBAPP_URL !== "http://localhost:3000" + ? [ + { + has: [ + { + type: "header", + key: "host", + value: "localhost:3000", + }, + ], + source: "/api/integrations/:args*", + destination: `${process.env.NEXT_PUBLIC_WEBAPP_URL}/api/integrations/:args*`, + permanent: false, + }, + ] + : []), + ]; - return redirects; - }, + if (process.env.NEXT_PUBLIC_WEBAPP_URL === "https://app.cal.com") { + redirects.push( + { + source: "/apps/dailyvideo", + destination: "/apps/daily-video", + permanent: true, + }, + { + source: "/apps/huddle01_video", + destination: "/apps/huddle01", + permanent: true, + }, + { + source: "/apps/jitsi_video", + destination: "/apps/jitsi", + permanent: true, + } + ); + } + + return redirects; + }, + }; }; -module.exports = () => plugins.reduce((acc, next) => next(acc), nextConfig); +module.exports = (phase) => plugins.reduce((acc, next) => next(acc), nextConfig(phase));