diff --git a/packages/embeds/README.md b/packages/embeds/README.md
index 8c05df0520..e0e9f885f9 100644
--- a/packages/embeds/README.md
+++ b/packages/embeds/README.md
@@ -166,8 +166,8 @@ For example, if your page is `https.example.com/contact`, you can enable logging
#### Routing Prerendering System
The prerendering system optimizes the initial load:
```typescript
-Cal.prerender({
- calLink: "organization/event-type",
+Cal("prerender", {
+ calLink,
type: "modal"
});
```
@@ -178,24 +178,63 @@ Key aspects:
- Loads the booking page but doesn't send the slots availability request
- Tries to reuse whenever it makes sense and do a fresh load otherwise
-Iframe Reuse and Reload Conditions. There could be three situations:
+Modal's Iframe Reuse and Reload Conditions. There could be four situations:
-1. Reuse
-2. Reuse the iframe but refetch the slots
-3. Do a fresh load in iframe
+1. Show modal as is. No connect and no requests happen - Corresponding action is "noAction"
+2. Connect but don't fetch the slots- Corresponding action is "connect-no-slots-fetch"
+3. Connect and fetch the slots - Corresponding action is "connect"
+4. Do a fresh load in iframe. Corresponding action is "fullReload"
-- **Reuse**:
- - Modal opens when
- - Modal is not in a failed state
- - config, params are same as the last time
- - No threshold violations
+- **Show modal as is**:
+ - Modal is not in a failed state
+ - config, params are same as the last time
+ - No time threshold violations
-- **Reuse the iframe but refetch the slots**:
+- **Connect but don't fetch the slots**:
- Only embed `config` changes (handled via "connect" flow)
- Query query params changes (handled via "connect" flow)
- Crossed slots stale time threshold (EMBED_MODAL_IFRAME_SLOT_STALE_TIME)
-- **Fresh Reload Conditions**:
+- **Connect and fetch the slots**:
+ - Slots are stale
+ - Crossed slots stale time threshold (EMBED_MODAL_IFRAME_SLOT_STALE_TIME)
+
+- **Do a fresh load in iframe**:
- Different path being loaded(i.e. /pro vs /free)
- Modal is in a failed state
- Time since last render exceeds EMBED_MODAL_IFRAME_FORCE_RELOAD_THRESHOLD_MS
+
+
+#### Prerendering headless router
+
+**Without namespace:**
+Prerender when there are high chances of user clicking the CTA.
+```js
+Cal('prerender', {
+ calLink: "router?formId=123&ONLY_THOSE_FIELDS_THAT_ARE_REQUIRED_BY_ROUTING_RULES_SHOULD_BE_PRESENT_HERE",
+ // Prerender right now works only with "modal", so 'element click' embed is able to reuse this prerendered iframe
+ type: "modal",
+ // Shows skeleton loader for a Team Event's booking slots page
+ pageType: "team.event.booking.slots"
+});
+```
+Using the prerendered iframe with a CTA:
+```js
+
+```
+
+**With namespace:**
+Prerender when there are high changes of user clicking the CTA.
+```js
+Cal.ns.myNamespace('prerender', {
+ calLink: "router?formId=123&ONLY_THOSE_FIELDS_THAT_ARE_REQUIRED_BY_ROUTING_RULES_SHOULD_BE_PRESENT_HERE",
+ // Prerender right now works only with "modal", so 'element click' embed is able to reuse this prerendered iframe
+ type: "modal"
+ // Shows skeleton loader for a Team Event's booking slots page
+ pageType: "team.event.booking.slots"
+});
+```
+Using the prerendered iframe with a CTA:
+```js
+
+```
\ No newline at end of file
diff --git a/packages/embeds/embed-core/src/constants.ts b/packages/embeds/embed-core/src/constants.ts
index ef3d089037..16a7a3b63f 100644
--- a/packages/embeds/embed-core/src/constants.ts
+++ b/packages/embeds/embed-core/src/constants.ts
@@ -4,6 +4,8 @@ export const EMBED_MODAL_IFRAME_SLOT_STALE_TIME = 60 * 1000; // 1 minute
// Keep it high by default to avoid impact on loading time as it would cause a full reload of the iframe user can configure it via options.iframeForceReloadThresholdMs
// Also full reload means that response would be resubmitted.
export const EMBED_MODAL_IFRAME_FORCE_RELOAD_THRESHOLD_MS = 15 * 60 * 1000; // 15 minutes
+// Ensures that prerender instruction would never re-prerender within one minute as long as the link is the same.
+export const EMBED_MODAL_PRERENDER_PREVENT_THRESHOLD_MS = 1 * 60 * 1000; // 1 minute
if (EMBED_MODAL_IFRAME_SLOT_STALE_TIME > EMBED_MODAL_IFRAME_FORCE_RELOAD_THRESHOLD_MS) {
throw new Error(
diff --git a/packages/embeds/embed-core/src/embed.test.ts b/packages/embeds/embed-core/src/embed.test.ts
index 2194883a66..80d43b29d2 100644
--- a/packages/embeds/embed-core/src/embed.test.ts
+++ b/packages/embeds/embed-core/src/embed.test.ts
@@ -5,22 +5,13 @@ import { describe, it, expect, beforeEach, vi, beforeAll } from "vitest";
import {
EMBED_MODAL_IFRAME_SLOT_STALE_TIME,
EMBED_MODAL_IFRAME_FORCE_RELOAD_THRESHOLD_MS,
+ EMBED_MODAL_PRERENDER_PREVENT_THRESHOLD_MS,
} from "./constants";
-import { submitResponseAndGetRoutingResult } from "./utils";
vi.mock("./tailwindCss", () => ({
default: "mockedTailwindCss",
}));
-vi.mock("./utils", async () => {
- const actual = (await vi.importActual("./utils")) as any;
-
- return {
- ...actual,
- submitResponseAndGetRoutingResult: vi.fn(),
- };
-});
-
type ExpectedModalBoxAttrs = {
theme: string;
layout: string;
@@ -335,6 +326,7 @@ describe("Cal", () => {
__prerender: true,
};
calInstance.api.modal(modalArg);
+ expect(calInstance.isPrerendering).toBe(true);
const modalBox = expectCalModalBoxToBeInDocument({
state: "prerendering",
@@ -399,11 +391,82 @@ describe("Cal", () => {
});
});
+ it("Accidental repeat prerender prevention", async () => {
+ const modalArg = {
+ ...baseModalArgs,
+ calLink: "router?form=123&email=john@example.com",
+ };
+
+ const { status: status1 } = await calInstance.api.modal({
+ ...modalArg,
+ __prerender: true,
+ });
+ expect(status1).toBe("created");
+ const embedRenderStartTime = calInstance.embedRenderStartTime;
+
+ const { status: status2 } = await calInstance.api.modal({
+ ...modalArg,
+ __prerender: true,
+ });
+ expect(status2).toBe("prerender-prevented");
+ // embedRenderStartTime should not be updated prerender did not happen
+ expect(calInstance.embedRenderStartTime).toBe(embedRenderStartTime);
+ });
+
+ it("should allow repeat prerender after threshold time has passed", async () => {
+ vi.useFakeTimers();
+ const modalArg = {
+ ...baseModalArgs,
+ calLink: "router?form=123&email=john@example.com",
+ };
+
+ // First prerender
+ const { status: status1 } = await calInstance.api.modal({
+ ...modalArg,
+ __prerender: true,
+ });
+ expect(status1).toBe("created");
+
+ // Mock time passage beyond threshold
+ vi.advanceTimersByTime(EMBED_MODAL_PRERENDER_PREVENT_THRESHOLD_MS + 1000);
+
+ // Second prerender after threshold - should be ALLOWED
+ const { status: status2 } = await calInstance.api.modal({
+ ...modalArg,
+ __prerender: true,
+ });
+
+ expect(status2).toBe("created");
+
+ vi.useRealTimers();
+ });
+
+ it("Prerender with non-headless router link and then CTA click with headless router is not allowed", async () => {
+ const modalArg = {
+ ...baseModalArgs,
+ calLink: "john-doe/meeting",
+ };
+
+ const { status: status1 } = await calInstance.api.modal({
+ ...modalArg,
+ __prerender: true,
+ });
+ expect(status1).toBe("created");
+
+ await expect(
+ calInstance.api.modal({
+ ...modalArg,
+ calLink: "router?form=123&email=john@example.com",
+ })
+ ).rejects.toThrow("`prerender` instruction should have been fired with headless router path");
+ });
+
describe("Modal State Transitions", () => {
- it(`should handle prerender -> open(with prefill) -> reopen scenario`, () => {
+ it(`should handle prerender -> open(with prefill) -> reopen(with re-submission) scenario`, () => {
// Prerender the modal
const modalArg = {
...baseModalArgs,
+ calLink: "router?form=FORM_ID&routingField1=value1&routingField2=value2",
};
const { modalBoxUid, expectedConfig } = (function prerender(): {
@@ -412,6 +475,7 @@ describe("Cal", () => {
} {
log("Prerendering the modal");
calInstance.api.modal({ ...buildModalArg(modalArg), __prerender: true });
+ expect(calInstance.isPrerendering).toBe(true);
const expectedConfig = {
...modalArg.config,
embedType: "modal",
@@ -425,11 +489,14 @@ describe("Cal", () => {
pageType: null,
},
expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
+ pathname: `${new URL(modalArg.calLink, "https://baseurl.example").pathname}`,
searchParams: new URLSearchParams({
...expectedConfig,
prerender: "true",
- "cal.skipSlotsFetch": "true",
+ form: "FORM_ID",
+ "cal.queueFormResponse": "true",
+ routingField1: "value1",
+ routingField2: "value2",
}),
origin: null,
},
@@ -439,138 +506,14 @@ describe("Cal", () => {
return { modalBoxUid, expectedConfig };
})();
- const { modalArgWithPrefilledConfig } = (function prefill() {
- log("Opening the modal with prefill config");
- const modalArgWithPrefilledConfig = {
- ...modalArg,
- config: { ...modalArg.config, name: "John Doe", email: "john@example.com" },
- };
- // Second modal call with additional config value for prefilling
- calInstance.api.modal(modalArgWithPrefilledConfig);
-
- const expectedConfigAfterPrefilling = {
- ...expectedConfig,
- name: modalArgWithPrefilledConfig.config.name,
- email: modalArgWithPrefilledConfig.config.email,
- };
-
- expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
- expectedModalBoxAttrs: {
- // Expect the modal to go to loading state - which starts the loader animation
- state: "loading",
- theme: modalArg.config.theme,
- layout: modalArg.config.layout,
- pageType: null,
- uid: modalBoxUid,
- },
- expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
- searchParams: new URLSearchParams({
- ...expectedConfig,
- // It remains because internally we remove prerender=true in iframe, as it is connect mode
- prerender: "true",
- "cal.skipSlotsFetch": "true",
- }),
- origin: null,
- },
- });
-
- // This would update the iframe document URL through embed-iframe.
- expect(calInstance.doInIframe).toHaveBeenCalledWith({
- method: "connect",
- arg: {
- config: {
- ...expectedConfigAfterPrefilling,
- },
- params: {},
- },
- });
-
- vi.mocked(calInstance.doInIframe).mockClear();
- return { modalArgWithPrefilledConfig };
- })();
-
- (function reopen() {
- log("Reopening the same modal without any changes");
- calInstance.api.modal(modalArgWithPrefilledConfig);
-
- // Connect won't happen again
- expect(calInstance.doInIframe).not.toHaveBeenCalledWith(
- expect.objectContaining({
- method: "connect",
- })
- );
-
- expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
- expectedModalBoxAttrs: {
- // Expect the modal to just go to "reopened" state
- state: "reopened",
- theme: modalArg.config.theme,
- layout: modalArg.config.layout,
- pageType: null,
- uid: modalBoxUid,
- },
- expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
- searchParams: new URLSearchParams({
- ...expectedConfig,
- // It remains because we are just reopening the same modal
- prerender: "true",
- "cal.skipSlotsFetch": "true",
- }),
- origin: null,
- },
- });
-
- expect(document.querySelectorAll("cal-modal-box").length).toBe(1);
- })();
- });
-
- it(`should handle prerender -> open(with prefill) -> error -> loading scenario`, () => {
- // Prerender the modal
- const modalArg = {
- ...baseModalArgs,
- };
-
- const { modalBoxUid, modalBox, expectedConfig } = (function prerender() {
- calInstance.api.modal({ ...buildModalArg(modalArg), __prerender: true });
-
- const expectedConfig = {
- ...modalArg.config,
- embedType: "modal",
- };
-
- const { modalBox } = expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
- expectedModalBoxAttrs: {
- state: "prerendering",
- theme: modalArg.config.theme,
- layout: modalArg.config.layout,
- pageType: null,
- },
- expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
- searchParams: new URLSearchParams({
- ...expectedConfig,
- prerender: "true",
- "cal.skipSlotsFetch": "true",
- }),
- origin: null,
- },
- });
-
- const modalBoxUid = modalBox.getAttribute("uid");
- return { modalBoxUid, modalBox, expectedConfig };
- })();
-
const { modalArgWithPrefilledConfig, expectedConfigAfterPrefilling } = (function prefill() {
log("Opening the modal with prefill config");
const modalArgWithPrefilledConfig = {
...modalArg,
config: { ...modalArg.config, name: "John Doe", email: "john@example.com" },
};
-
// Second modal call with additional config value for prefilling
- calInstance.api.modal({ ...buildModalArg(modalArgWithPrefilledConfig) });
+ calInstance.api.modal(modalArgWithPrefilledConfig);
const expectedConfigAfterPrefilling = {
...expectedConfig,
@@ -588,24 +531,33 @@ describe("Cal", () => {
uid: modalBoxUid,
},
expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
+ pathname: `${new URL(modalArg.calLink, "https://baseurl.example").pathname}`,
searchParams: new URLSearchParams({
...expectedConfig,
- // It remains because internally we remove prerender=true in iframe - through connect flow
+ // It remains because internally we remove prerender=true in iframe, as it is connect mode
prerender: "true",
- "cal.skipSlotsFetch": "true",
+ form: "FORM_ID",
+ "cal.queueFormResponse": "true",
+ routingField1: "value1",
+ routingField2: "value2",
}),
origin: null,
},
});
+ // Right now we allow re-submission of the same form data, for which we need to connect again
expect(calInstance.doInIframe).toHaveBeenCalledWith({
method: "connect",
arg: {
config: {
+ "cal.embed.noSlotsFetchOnConnect": "true",
...expectedConfigAfterPrefilling,
},
- params: {},
+ params: {
+ routingField1: "value1",
+ routingField2: "value2",
+ form: "FORM_ID",
+ },
},
});
@@ -613,412 +565,52 @@ describe("Cal", () => {
return { modalArgWithPrefilledConfig, expectedConfigAfterPrefilling };
})();
- log("Simulating an error state");
- modalBox.setAttribute("state", "failed");
-
(function reopen() {
- log("Reopening the same modal but it is in error state, so full page load would be initiated");
+ log("Reopening the same modal without any changes");
calInstance.api.modal(modalArgWithPrefilledConfig);
+ expect(calInstance.isPrerendering).toBe(false);
- expect(calInstance.doInIframe).not.toHaveBeenCalledWith(
- expect.objectContaining({
- method: "connect",
- })
- );
-
- expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
- expectedModalBoxAttrs: {
- // Expect the modal to just go to "reopened" state
- state: "loading",
- theme: modalArg.config.theme,
- layout: modalArg.config.layout,
- pageType: null,
- uid: modalBoxUid,
- },
- expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
- searchParams: new URLSearchParams({
- ...expectedConfigAfterPrefilling,
- }),
- origin: null,
- },
- });
-
- expect(document.querySelectorAll("cal-modal-box").length).toBe(1);
- })();
- });
-
- it("should handle prerender(booking link) -> headless router submission -> reuse booking link ", async () => {
- // Prerender the modal
- const modalArg = {
- ...baseModalArgs,
- calLink: "sales/demo",
- };
-
- const { expectedConfig } = (function prerender(): { expectedConfig: Record } {
- log("Prerendering the modal");
- calInstance.api.modal({ ...buildModalArg(modalArg), __prerender: true });
-
- const expectedConfig = {
- ...modalArg.config,
- embedType: "modal",
- };
-
- expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
- expectedModalBoxAttrs: {
- state: "prerendering",
- theme: modalArg.config.theme,
- layout: modalArg.config.layout,
- pageType: null,
- },
- expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
- searchParams: new URLSearchParams({
- ...expectedConfig,
- prerender: "true",
- "cal.skipSlotsFetch": "true",
- }),
- origin: null,
- },
- });
-
- return { expectedConfig };
- })();
-
- vi.mocked(submitResponseAndGetRoutingResult).mockImplementation(() => {
- return Promise.resolve({
- // Redirecting to the booking link that is already prerendered
- redirect: `${process.env.EMBED_PUBLIC_WEBAPP_URL}/${modalArg.calLink}`,
- });
- });
-
- await (async function headlessRouterSubmission() {
- log("Submitting the routing form headless");
- const config = expectedConfig;
- const configWithPrefilledValues = {
- ...config,
- name: "John Doe",
- email: "john@example.com",
- };
-
- const modalOpenPromise = calInstance.api.modal({
- ...modalArg,
- calLink: "router?form=FORM_ID",
- config: configWithPrefilledValues,
- });
-
- // State must immediately change to loading, so that loader is shown
- const calModalBox = document.querySelector("cal-modal-box") as HTMLElement;
- expect(calModalBox.getAttribute("state")).toBe("loading");
- await modalOpenPromise;
- expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
- expectedModalBoxAttrs: {
- state: "loading",
- theme: modalArg.config.theme,
- layout: modalArg.config.layout,
- pageType: null,
- },
- expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
- searchParams: new URLSearchParams({
- ...config,
- // It remains because internally we remove prerender=true in iframe as it is connect mode
- prerender: "true",
- "cal.skipSlotsFetch": "true",
- }),
- origin: null,
- },
- });
-
- // Now check that we connected with the right config
+ // Connect won't happen again
expect(calInstance.doInIframe).toHaveBeenCalledWith(
expect.objectContaining({
method: "connect",
arg: {
config: {
- ...configWithPrefilledValues,
+ "cal.embed.noSlotsFetchOnConnect": "true",
+ embedType: "modal",
+ ...expectedConfigAfterPrefilling,
+ },
+ params: {
+ routingField1: "value1",
+ routingField2: "value2",
+ form: "FORM_ID",
},
- params: {},
},
})
);
- })();
- });
- it("should handle prerender(booking link) -> headless router submission -> different booking link redirect -> fresh page load", async () => {
- // Prerender the modal
- const modalArg = {
- ...baseModalArgs,
- calLink: "sales/demo",
- };
+ // expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
+ // expectedModalBoxAttrs: {
+ // // Expect the modal to just go to "reopened" state
+ // state: "reopened",
+ // theme: modalArg.config.theme,
+ // layout: modalArg.config.layout,
+ // pageType: null,
+ // uid: modalBoxUid,
+ // },
+ // expectedIframeUrlObject: {
+ // pathname: `/${modalArg.calLink}`,
+ // searchParams: new URLSearchParams({
+ // ...expectedConfig,
+ // // It remains because we are just reopening the same modal
+ // prerender: "true",
+ // "cal.skipSlotsFetch": "true",
+ // }),
+ // origin: null,
+ // },
+ // });
- const { expectedConfig } = (function prerender(): { expectedConfig: Record } {
- log("Prerendering the modal");
- calInstance.api.modal({ ...buildModalArg(modalArg), __prerender: true });
-
- const expectedConfig = {
- ...modalArg.config,
- embedType: "modal",
- };
-
- expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
- expectedModalBoxAttrs: {
- state: "prerendering",
- theme: modalArg.config.theme,
- layout: modalArg.config.layout,
- pageType: null,
- },
- expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
- searchParams: new URLSearchParams({
- ...expectedConfig,
- prerender: "true",
- "cal.skipSlotsFetch": "true",
- }),
- origin: null,
- },
- });
-
- return { expectedConfig };
- })();
-
- const headlessRouterRedirectUrlWithPathOnly = `/something_else_than_prerendered_link`;
- vi.mocked(submitResponseAndGetRoutingResult).mockImplementation(() => {
- return Promise.resolve({
- // Redirecting to the booking link that is already prerendered
- redirect: `${process.env.EMBED_PUBLIC_WEBAPP_URL}${headlessRouterRedirectUrlWithPathOnly}?routingFormResponseId=xyz`,
- });
- });
-
- await (async function headlessRouterSubmission() {
- log("Submitting the modal");
- const config = expectedConfig;
- const configWithPrefilledValues = {
- ...config,
- name: "John Doe",
- email: "john@example.com",
- };
- const configWithPrefilledValuesAndHeadlessRouterRedirectParams = {
- ...configWithPrefilledValues,
- routingFormResponseId: "xyz",
- };
-
- const modalOpenPromise = calInstance.api.modal({
- ...modalArg,
- calLink: "router?form=FORM_ID",
- config: configWithPrefilledValues,
- });
-
- const calModalBox = document.querySelector("cal-modal-box") as HTMLElement;
- expect(calModalBox.getAttribute("state")).toBe("loading");
- await modalOpenPromise;
-
- expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
- expectedModalBoxAttrs: {
- state: "loading",
- theme: modalArg.config.theme,
- layout: modalArg.config.layout,
- pageType: null,
- },
- expectedIframeUrlObject: {
- pathname: headlessRouterRedirectUrlWithPathOnly,
- searchParams: new URLSearchParams({
- ...configWithPrefilledValuesAndHeadlessRouterRedirectParams,
- }),
- origin: null,
- },
- });
-
- // Now check that we connected with the right config
- expect(calInstance.doInIframe).not.toHaveBeenCalledWith(
- expect.objectContaining({
- method: "connect",
- })
- );
- })();
- });
-
- it("should handle prerender(booking link) -> headless router submission -> Message ", async () => {
- // Prerender the modal
- const modalArg = {
- ...baseModalArgs,
- calLink: "sales/demo",
- };
-
- const { expectedConfig } = (function prerender(): { expectedConfig: Record } {
- log("Prerendering the modal");
- calInstance.api.modal({ ...buildModalArg(modalArg), __prerender: true });
-
- const expectedConfig = {
- ...modalArg.config,
- embedType: "modal",
- };
-
- expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
- expectedModalBoxAttrs: {
- state: "prerendering",
- theme: modalArg.config.theme,
- layout: modalArg.config.layout,
- pageType: null,
- },
- expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
- searchParams: new URLSearchParams({
- ...expectedConfig,
- prerender: "true",
- "cal.skipSlotsFetch": "true",
- }),
- origin: null,
- },
- });
-
- return { expectedConfig };
- })();
-
- vi.mocked(submitResponseAndGetRoutingResult).mockImplementation(() => {
- return Promise.resolve({
- status: "success",
- // Redirecting to the booking link that is already prerendered
- message: "Fallback Route Message",
- });
- });
-
- await (async function headlessRouterSubmission() {
- log("Submitting the modal");
- const config = expectedConfig;
- const configWithPrefilledValues = {
- ...config,
- name: "John Doe",
- email: "john@example.com",
- };
-
- const modalOpenPromise = calInstance.api.modal({
- ...modalArg,
- calLink: "router?form=FORM_ID",
- config: configWithPrefilledValues,
- });
-
- const calModalBox = document.querySelector("cal-modal-box") as HTMLElement;
- expect(calModalBox.getAttribute("state")).toBe("loading");
- await modalOpenPromise;
-
- expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
- expectedModalBoxAttrs: {
- state: "has-message",
- theme: modalArg.config.theme,
- layout: modalArg.config.layout,
- pageType: null,
- },
- expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
- searchParams: new URLSearchParams({
- ...expectedConfig,
- prerender: "true",
- "cal.skipSlotsFetch": "true",
- }),
- origin: null,
- },
- });
-
- // Now check that we connected with the right config
- expect(calInstance.doInIframe).not.toHaveBeenCalledWith(
- expect.objectContaining({
- method: "connect",
- })
- );
-
- expect(calModalBox.getAttribute("data-message")).toBe("Fallback Route Message");
- })();
- });
-
- it("should handle prerender(booking link) -> headless router submission -> Error Message", async () => {
- // Prerender the modal
- const modalArg = {
- ...baseModalArgs,
- calLink: "sales/demo",
- };
-
- const { expectedConfig } = (function prerender(): { expectedConfig: Record } {
- log("Prerendering the modal");
- calInstance.api.modal({ ...buildModalArg(modalArg), __prerender: true });
-
- const expectedConfig = {
- ...modalArg.config,
- embedType: "modal",
- };
-
- expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
- expectedModalBoxAttrs: {
- state: "prerendering",
- theme: modalArg.config.theme,
- layout: modalArg.config.layout,
- pageType: null,
- },
- expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
- searchParams: new URLSearchParams({
- ...expectedConfig,
- prerender: "true",
- "cal.skipSlotsFetch": "true",
- }),
- origin: null,
- },
- });
-
- return { expectedConfig };
- })();
-
- vi.mocked(submitResponseAndGetRoutingResult).mockImplementation(() => {
- return Promise.resolve({
- error: "Fallback Route Error",
- });
- });
-
- await (async function headlessRouterSubmission() {
- log("Submitting the modal");
- const config = expectedConfig;
- const configWithPrefilledValues = {
- ...config,
- name: "John Doe",
- email: "john@example.com",
- };
-
- const modalOpenPromise = calInstance.api.modal({
- ...modalArg,
- calLink: "router?form=FORM_ID",
- config: configWithPrefilledValues,
- });
-
- const calModalBox = document.querySelector("cal-modal-box") as HTMLElement;
- expect(calModalBox.getAttribute("state")).toBe("loading");
- await modalOpenPromise;
-
- expectCalModalBoxToBeInDocumentWithIframeHavingUrl({
- expectedModalBoxAttrs: {
- state: "failed",
- theme: modalArg.config.theme,
- layout: modalArg.config.layout,
- pageType: null,
- },
- expectedIframeUrlObject: {
- pathname: `/${modalArg.calLink}`,
- searchParams: new URLSearchParams({
- ...expectedConfig,
- prerender: "true",
- "cal.skipSlotsFetch": "true",
- }),
- origin: null,
- },
- });
-
- // Now check that we connected with the right config
- expect(calInstance.doInIframe).not.toHaveBeenCalledWith(
- expect.objectContaining({
- method: "connect",
- })
- );
- expect(calModalBox.getAttribute("data-error-code")).toBe("routerError");
- expect(calModalBox.getAttribute("data-message")).toBe("Fallback Route Error");
+ expect(document.querySelectorAll("cal-modal-box").length).toBe(1);
})();
});
});
diff --git a/packages/embeds/embed-core/src/embed.ts b/packages/embeds/embed-core/src/embed.ts
index 3251e25e0d..86b68bf1cd 100644
--- a/packages/embeds/embed-core/src/embed.ts
+++ b/packages/embeds/embed-core/src/embed.ts
@@ -6,21 +6,22 @@ import { addAppCssVars } from "./addAppCssVars";
import {
EMBED_MODAL_IFRAME_FORCE_RELOAD_THRESHOLD_MS,
EMBED_MODAL_IFRAME_SLOT_STALE_TIME,
+ EMBED_MODAL_PRERENDER_PREVENT_THRESHOLD_MS,
} from "./constants";
import type { InterfaceWithParent, interfaceWithParent } from "./embed-iframe";
import css from "./embed.css";
import { SdkActionManager } from "./sdk-action-manager";
import type { EventData, EventDataMap } from "./sdk-action-manager";
import tailwindCss from "./tailwindCss";
-import type { UiConfig, EmbedPageType, PrefillAndIframeAttrsConfig } from "./types";
+import type { UiConfig, EmbedPageType, PrefillAndIframeAttrsConfig, ModalPrerenderOptions } from "./types";
import { getMaxHeightForModal } from "./ui-utils";
import {
fromEntriesWithDuplicateKeys,
isRouterPath,
- submitResponseAndGetRoutingResult,
generateDataAttributes,
getConfigProp,
isSameBookingLink,
+ buildConfigWithPrerenderRelatedFields,
} from "./utils";
// Exporting for consumption by @calcom/embed-core user
@@ -51,12 +52,8 @@ type CalConfig = {
uiDebug?: boolean;
};
-type ModalPrerenderOptions = {
- slotsStaleTimeMs?: number;
- iframeForceReloadThresholdMs?: number;
- reuseFully?: boolean;
- // Default value is true right now but it would change soon
- backgroundSlotsFetch?: boolean;
+type ModalBoxElement = Element & {
+ uid?: string;
};
type ModalStateData = {
@@ -221,7 +218,7 @@ export class Cal {
__config: CalConfig;
- modalBox?: Element;
+ modalBox?: ModalBoxElement;
inlineEl?: Element;
@@ -688,89 +685,128 @@ export class Cal {
return new URL(`${urlObject.pathname}${urlObject.search}`, urlObject.origin);
}
- async submitThroughHeadlessRouterInModal({
- modal,
- calLinkUrlObject,
- stateData,
- }: {
- modal: { uid: string; element: Element; calOrigin: string | null };
- calLinkUrlObject: URL;
- stateData: ModalStateData;
- }) {
- const { uid: modalBoxUid, element: modalEl, calOrigin: _calOrigin } = modal;
- const { embedConfig } = stateData;
+ // We record the updated params on dataset.calLink which allows us to determine if something actually changed since the last time we loaded
+ // This is used by getNextActionForModal
+ recordUpdatedParamsForIframe(params: Record) {
const lastLoadedUrlInIframeObject = this.getLastLoadedLinkInframe();
- const lastLoadedPathInIframe = lastLoadedUrlInIframeObject?.pathname ?? null;
- const calConfig = this.getCalConfig();
- const calOrigin = _calOrigin ?? calConfig.calOrigin;
-
- const headlessRouterPageObject = calLinkUrlObject;
- const result = await submitResponseAndGetRoutingResult({
- headlessRouterPageUrl: headlessRouterPageObject.toString(),
- });
- log("Headless router result", result);
- if ("redirect" in result) {
- const routerRedirectUrl = new URL(result.redirect);
- const paramsFromRedirect = fromEntriesWithDuplicateKeys(routerRedirectUrl.searchParams.entries());
-
- const newEmbedConfig = (this.embedConfig = {
- ...embedConfig,
- ...paramsFromRedirect,
- });
- const actionToTake = this.getNextActionForModal({
- modal: { uid: modalBoxUid },
- pathWithQueryToLoad: `${routerRedirectUrl.pathname}${routerRedirectUrl.search}`,
- stateData: {
- ...stateData,
- embedConfig: newEmbedConfig,
- },
- });
- if (actionToTake === "fullReload") {
- if (lastLoadedPathInIframe) {
- console.error("Preloaded iframe couldn't be used", {
- preloadedPath: lastLoadedPathInIframe,
- newPath: routerRedirectUrl.pathname,
- });
- }
- const pathWithoutStartingSlash = routerRedirectUrl.pathname.replace(/^\//, "");
- this.loadInIframe({
- calLink: pathWithoutStartingSlash,
- calOrigin,
- config: newEmbedConfig,
- iframe: this.iframe as HTMLIFrameElement,
- });
- } else {
- log("Reusing the same iframe for headless router", {
- routerRedirectUrl: routerRedirectUrl.toString(),
- lastLoadedPathInIframe,
- });
-
- // Connection Initiated
- this.doInIframe({
- method: "connect",
- arg: {
- config: newEmbedConfig,
- params: fromEntriesWithDuplicateKeys(routerRedirectUrl.searchParams.entries()),
- },
- });
- }
- } else if ("message" in result) {
- log("Setting message in modal", {
- message: result.message,
- });
- // TODO: We might need to sanitize the error message and error code before setting it
- modalEl.setAttribute("data-message", result.message);
- modalEl.setAttribute("state", "has-message");
- } else if ("error" in result) {
- log("Setting error in modal", {
- error: result.error,
- });
- // We need to show this message in the modal
- modalEl.setAttribute("data-error-code", "routerError");
- // TODO: We might need to sanitize the error message and error code before setting it
- modalEl.setAttribute("data-message", result.error);
- modalEl.setAttribute("state", "failed");
+ if (!lastLoadedUrlInIframeObject || !this.iframe) {
+ return;
}
+ // Create a clone of the last loaded url object in iframe
+ const urlObject = new URL(lastLoadedUrlInIframeObject.toString());
+ for (const [key, value] of Object.entries(params)) {
+ urlObject.searchParams.set(key, value as string);
+ }
+ this.iframe.dataset.calLink = `${urlObject.pathname.replace(/^\//, "")}${urlObject.search}`;
+ }
+
+ canPrerenderLink({
+ calLink,
+ calOrigin,
+ previousEmbedRenderStartTime,
+ }: {
+ calLink: string;
+ calOrigin: string;
+ previousEmbedRenderStartTime: number | null;
+ }) {
+ const lastLoadedUrlInIframeObject = this.getLastLoadedLinkInframe();
+ // Trying to prerender a link that is already prerendered.
+ // Prevent unnecessary repeat prerenders
+ if (lastLoadedUrlInIframeObject?.toString() === new URL(calLink, calOrigin).toString()) {
+ const isThresholdCrossed = hasCrossedThreshold();
+ if (isThresholdCrossed) {
+ log("Threshold crossed, allowing repeat prerender");
+ return true;
+ }
+ log("Threshold not crossed, preventing repeat prerender");
+ return false;
+ }
+ return true;
+
+ function hasCrossedThreshold() {
+ if (!previousEmbedRenderStartTime) {
+ return false;
+ }
+ const timeDiff = Date.now() - previousEmbedRenderStartTime;
+ if (timeDiff >= EMBED_MODAL_PRERENDER_PREVENT_THRESHOLD_MS) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ prepareForPrerender({
+ calLink,
+ calOrigin,
+ previousEmbedRenderStartTime,
+ prerenderOptions,
+ isHeadlessRouterPath,
+ }: {
+ calLink: string;
+ calOrigin: string;
+ previousEmbedRenderStartTime: number | null;
+ prerenderOptions: ModalPrerenderOptions;
+ isHeadlessRouterPath: boolean;
+ }) {
+ if (!this.canPrerenderLink({ calLink, calOrigin, previousEmbedRenderStartTime })) {
+ return null;
+ }
+
+ if (this.modalBox) {
+ log("Destroying previous prerendered modalbox");
+ // If we are re-prerendering, we destroy the previous modalbox, allowing user to prerender as many times as they want
+ this.modalBox.remove();
+ }
+
+ const DEFAULT_BACKGROUND_SLOTS_FETCH = isHeadlessRouterPath ? true : false;
+
+ const backgroundSlotsFetch =
+ typeof prerenderOptions.backgroundSlotsFetch === "undefined"
+ ? DEFAULT_BACKGROUND_SLOTS_FETCH
+ : prerenderOptions.backgroundSlotsFetch;
+ const enrichedPrerenderOptions = { ...prerenderOptions, backgroundSlotsFetch };
+
+ // Persist prerender options here so that during CTA click we can use these.
+ this.api.prerenderOptions = enrichedPrerenderOptions;
+ this.isPrerendering = true;
+
+ return { enrichedPrerenderOptions };
+ }
+
+ setModalRenderStartVariables({
+ embedConfig,
+ embedRenderStartTime,
+ }: {
+ embedConfig: PrefillAndIframeAttrsConfig;
+ embedRenderStartTime: number;
+ }) {
+ this.embedConfig = embedConfig;
+ this.embedRenderStartTime = embedRenderStartTime;
+ }
+
+ getPreviousModalRenderStartVariables() {
+ return {
+ embedConfig: this.embedConfig,
+ embedRenderStartTime: this.embedRenderStartTime,
+ };
+ }
+
+ connect({
+ config,
+ params,
+ }: {
+ config: PrefillAndIframeAttrsConfig;
+ params: Record;
+ }) {
+ // Update the iframe query params and record the change as well on the iframe for getNextActionForModal to use
+ this.recordUpdatedParamsForIframe(params);
+ this.doInIframe({
+ method: "connect",
+ arg: {
+ config,
+ params,
+ },
+ });
}
}
@@ -979,56 +1015,24 @@ class CalApi {
__prerender?: boolean;
prerenderOptions?: ModalPrerenderOptions;
}) {
+ const isPrerendering = !!__prerender;
+ if (typeof config.iframeAttrs === "string" || config.iframeAttrs instanceof Array) {
+ throw new Error("iframeAttrs should be an object");
+ }
+ const containerEl = document.body;
const calConfig = this.cal.getCalConfig();
// Clone so that it doesn't mutate the original config
config = { ...config };
+
// calOrigin could have been passed as empty string by the user
calOrigin = calOrigin || calConfig.calOrigin;
+
const calLinkUrlObject = new URL(calLink, calOrigin);
const isHeadlessRouterPath = calLinkUrlObject ? isRouterPath(calLinkUrlObject.toString()) : false;
+ const { embedConfig: previousEmbedConfig, embedRenderStartTime: previousEmbedRenderStartTime } =
+ this.cal.getPreviousModalRenderStartVariables();
- if (__prerender && this.cal.modalBox) {
- log("Destroying previous prerendered modalbox");
- // If we are re-prerendering, we destroy the previous modalbox, allowing user to prerender as many times as they want
- this.cal.modalBox.remove();
- }
-
- // `this.modalUid` is set in non-preload case(Temporarily not being-set)
- // `this.prerenderedModalUid` is set for a modal created through "prerender"
- const uid = this.modalUid || this.prerenderedModalUid || String(Date.now()) || "0";
- // Means whether there is already an attempt to use the prerendered modal
- const isConnectionInitiated = !!(this.modalUid && this.prerenderedModalUid);
-
- const containerEl = document.body;
- this.cal.isPrerendering = !!__prerender;
- if (__prerender) {
- // While prerendering headless router path, we by default want to fetch slots in background(for the time being)
- // For other prerenderings, we don't want to fetch slots in background - Keeping the behaviour same as before
- const DEFAULT_BACKGROUND_SLOTS_FETCH = isHeadlessRouterPath ? true : false;
- const backgroundSlotsFetch =
- typeof prerenderOptions.backgroundSlotsFetch === "undefined"
- ? DEFAULT_BACKGROUND_SLOTS_FETCH
- : prerenderOptions.backgroundSlotsFetch;
-
- this.prerenderOptions = {
- ...prerenderOptions,
- backgroundSlotsFetch,
- };
- // Add prerender query param
- config.prerender = "true";
-
- // If we are prerendering a headless router path, we don't want to record the response immediately.
- if (isHeadlessRouterPath) {
- config["cal.queueFormResponse"] = "true";
- }
-
- if (!backgroundSlotsFetch) {
- // When prerendering, we don't want to preload slots as they might be outdated anyway by the time they are used
- // Also, when used with Headless Router attributes setup, we might endup fetching slots for a lot of people, which would be a waste and unnecessary load on Cal.com resources
- config["cal.skipSlotsFetch"] = "true";
- }
- }
-
+ let enrichedConfig;
const configWithGuestKeyAndColorScheme = withColorScheme(
Cal.ensureGuestKey({
...config,
@@ -1037,41 +1041,99 @@ class CalApi {
containerEl
);
- const previousEmbedRenderStartTime = this.cal.embedRenderStartTime;
- const previousEmbedConfig = this.cal.embedConfig;
+ let enrichedPrerenderOptions;
+ if (isPrerendering) {
+ const preparationResult = this.cal.prepareForPrerender({
+ calLink,
+ calOrigin,
+ previousEmbedRenderStartTime,
+ prerenderOptions,
+ isHeadlessRouterPath,
+ });
+ if (!preparationResult) {
+ log(`Prevented unnecessary repeat prerender for ${calLink}`);
+ return {
+ status: "prerender-prevented",
+ };
+ }
+
+ enrichedPrerenderOptions = preparationResult.enrichedPrerenderOptions;
+ enrichedConfig = buildConfigWithPrerenderRelatedFields({
+ config: configWithGuestKeyAndColorScheme,
+ isHeadlessRouterPath,
+ backgroundSlotsFetch: !!this.prerenderOptions?.backgroundSlotsFetch,
+ });
+ } else {
+ this.cal.isPrerendering = false;
+ enrichedConfig = configWithGuestKeyAndColorScheme;
+ }
+
+ // `this.modalUid` is set in non-preload case(Temporarily not being-set)
+ // `this.prerenderedModalUid` is set for a modal created through "prerender"
+ const reusableModalUid = this.modalUid || this.prerenderedModalUid;
+ const uid = reusableModalUid || String(Date.now());
+ // Means whether there is already an attempt to use the prerendered modal
+ const isConnectionInitiated = !!(this.modalUid && this.prerenderedModalUid);
const embedRenderStartTime = Date.now();
- this.cal.embedRenderStartTime = embedRenderStartTime;
- this.cal.embedConfig = configWithGuestKeyAndColorScheme;
+
+ const stateData = {
+ embedConfig: enrichedConfig,
+ previousEmbedConfig,
+ embedRenderStartTime,
+ previousEmbedRenderStartTime,
+ isConnectionInitiated,
+ // If prerenderOptions are available, use them. Otherwise use the prerenderOptions from the previous call to modal for prerender
+ prerenderOptions: enrichedPrerenderOptions ?? this.prerenderOptions ?? null,
+ };
+
+ this.cal.setModalRenderStartVariables({
+ embedConfig: enrichedConfig,
+ embedRenderStartTime,
+ });
+
+ return this.__modal({
+ calLink,
+ enrichedConfig,
+ calOrigin,
+ isPrerendering,
+ stateData,
+ uid,
+ containerEl,
+ });
+ }
+
+ async __modal({
+ uid,
+ calLink,
+ enrichedConfig,
+ calOrigin,
+ isPrerendering,
+ stateData,
+ containerEl,
+ }: {
+ uid: string;
+ calLink: string;
+ enrichedConfig: PrefillAndIframeAttrsConfig;
+ calOrigin: string;
+ isPrerendering: boolean;
+ stateData: ModalStateData;
+ containerEl: HTMLElement;
+ }) {
+ const calLinkUrlObject = new URL(calLink, calOrigin);
+ const isHeadlessRouterPath = calLinkUrlObject ? isRouterPath(calLinkUrlObject.toString()) : false;
const existingModalEl = document.querySelector(`cal-modal-box[uid="${uid}"]`);
-
// isConnectionPossible
if (!!existingModalEl && !!this.cal.iframe) {
log(`Trying to reuse modal ${uid}`);
- const stateData = {
- embedConfig: configWithGuestKeyAndColorScheme,
- previousEmbedConfig,
- embedRenderStartTime,
- previousEmbedRenderStartTime,
- isConnectionInitiated,
- prerenderOptions: this.prerenderOptions ?? null,
- };
const lastLoadedUrlObject = this.cal.getLastLoadedLinkInframe();
const lastLoadedPathIsRouter = lastLoadedUrlObject?.pathname?.includes("/router");
- // TODO: This branch needs to be removed as we don't want to support this use case anymore.
if (isHeadlessRouterPath && !lastLoadedPathIsRouter) {
- // Immediately take it to loading state. Either through connect or through loadInIframe, it would later be updated
- existingModalEl.setAttribute("state", "loading");
-
- // submitThroughHeadlessRouterInModal would further decide whether full page reload is needed or a connect would suffice
- // actionToTake might be "fullReload" and still connect could work in case of headless router because there might be just query params change(which can be handled by connect) of calLink(i.e. ?form=formid&newParam=newValue)
- await this.cal.submitThroughHeadlessRouterInModal({
- modal: { uid, element: existingModalEl, calOrigin },
- calLinkUrlObject,
- stateData,
- });
+ throw new Error(
+ "`prerender` instruction should have been fired with headless router path as calLink(i.e. router?form={FORM_ID}&PARAMS=VALUES)"
+ );
} else {
const actionToTake = this.cal.getNextActionForModal({
modal: { uid },
@@ -1080,7 +1142,7 @@ class CalApi {
});
if (actionToTake === "noAction") {
- if (__prerender) {
+ if (isPrerendering) {
// A prerender instruction can never show the modal
return;
}
@@ -1100,22 +1162,20 @@ class CalApi {
calLink,
calOrigin,
iframe: this.cal.iframe,
- config: configWithGuestKeyAndColorScheme,
+ config: enrichedConfig,
});
} else if (actionToTake === "connect" || actionToTake === "connect-no-slots-fetch") {
- this.cal.doInIframe({
- method: "connect",
- arg: {
- config: {
- ...configWithGuestKeyAndColorScheme,
- ...(actionToTake === "connect-no-slots-fetch"
- ? {
- "cal.embed.noSlotsFetchOnConnect": "true",
- }
- : {}),
- },
- params: fromEntriesWithDuplicateKeys(calLinkUrlObject.searchParams.entries()),
+ const paramsToAdd = fromEntriesWithDuplicateKeys(calLinkUrlObject.searchParams.entries());
+ this.cal.connect({
+ config: {
+ ...enrichedConfig,
+ ...(actionToTake === "connect-no-slots-fetch"
+ ? {
+ "cal.embed.noSlotsFetchOnConnect": "true",
+ }
+ : {}),
},
+ params: paramsToAdd,
});
}
}
@@ -1127,23 +1187,19 @@ class CalApi {
log(`Creating new modal ${uid}`);
- if (__prerender) {
+ if (isPrerendering) {
this.prerenderedModalUid = uid;
} else {
// Intentionally not setting it to avoid the behaviour of reusing the same modal. It was disabled earlier but now can be enabled but we will enable it later.
// this.modalUid = uid;
}
- if (typeof config.iframeAttrs === "string" || config.iframeAttrs instanceof Array) {
- throw new Error("iframeAttrs should be an object");
- }
-
let iframe = null;
if (!iframe) {
iframe = this.cal.createIframe({
calLink,
- config: configWithGuestKeyAndColorScheme,
+ config: enrichedConfig,
calOrigin,
});
}
@@ -1152,9 +1208,9 @@ class CalApi {
iframe.style.height = "100%";
iframe.style.width = "100%";
const template = document.createElement("template");
- const pageType = getConfigProp(configWithGuestKeyAndColorScheme, "cal.embed.pageType");
- const theme = getConfigProp(configWithGuestKeyAndColorScheme, "theme");
- const layout = getConfigProp(configWithGuestKeyAndColorScheme, "layout");
+ const pageType = getConfigProp(enrichedConfig, "cal.embed.pageType");
+ const theme = getConfigProp(enrichedConfig, "theme");
+ const layout = getConfigProp(enrichedConfig, "layout");
template.innerHTML = `>;
export type setNonStylesConfig = React.Dispatch>;
+
+export type ModalPrerenderOptions = {
+ slotsStaleTimeMs?: number;
+ iframeForceReloadThresholdMs?: number;
+ backgroundSlotsFetch?: boolean;
+};
diff --git a/packages/embeds/embed-core/src/utils.ts b/packages/embeds/embed-core/src/utils.ts
index 5955e51e79..b26c2ed42f 100644
--- a/packages/embeds/embed-core/src/utils.ts
+++ b/packages/embeds/embed-core/src/utils.ts
@@ -196,3 +196,32 @@ export function buildSearchParamsFromConfig(config: PrefillAndIframeAttrsConfig)
}
return searchParams;
}
+
+export function buildConfigWithPrerenderRelatedFields({
+ config,
+ isHeadlessRouterPath,
+ backgroundSlotsFetch,
+}: {
+ config: PrefillAndIframeAttrsConfig;
+ isHeadlessRouterPath: boolean;
+ backgroundSlotsFetch: boolean;
+}) {
+ // While prerendering headless router path, we by default want to fetch slots in background(for the time being)
+ // For other prerenderings, we don't want to fetch slots in background - Keeping the behaviour same as before
+
+ // If we are prerendering a headless router path, we don't want to record the response immediately.
+ if (isHeadlessRouterPath) {
+ config["cal.queueFormResponse"] = "true";
+ }
+
+ if (!backgroundSlotsFetch) {
+ // When prerendering, we don't want to preload slots as they might be outdated anyway by the time they are used
+ // Also, when used with Headless Router attributes setup, we might endup fetching slots for a lot of people, which would be a waste and unnecessary load on Cal.com resources
+ config["cal.skipSlotsFetch"] = "true";
+ }
+
+ return {
+ ...config,
+ prerender: "true",
+ };
+}