From e752cff2d2161ac0433ccd644bdc677dfe28a924 Mon Sep 17 00:00:00 2001 From: SvenAlHamad Date: Mon, 3 Aug 2026 15:57:45 +0200 Subject: [PATCH 1/3] feat(app-website-builder): detect missing frontend in the editor preview When no frontend is running at the preview domain, the editor used to sit on the "Loading preview..." spinner forever. Detect it and offer a way forward: read the setup instructions, or connect to the hosted sample frontend. Co-Authored-By: Claude Opus 5 (1M context) --- .../Content/ContentPreviewConfig.tsx | 6 + .../defaultConfig/Content/Preview/Iframe.tsx | 22 +-- .../Content/Preview/NoFrontendConnected.tsx | 102 ++++++++++++++ .../defaultConfig/Content/Preview/Preview.tsx | 16 +-- .../Content/Preview/PreviewFrame.tsx | 51 +++++++ .../Content/Preview/usePreviewConnection.ts | 127 ++++++++++++++++++ .../Content/SampleFrontendBanner.tsx | 30 +++++ .../defaultConfig/Content/sampleFrontend.ts | 10 ++ 8 files changed, 347 insertions(+), 17 deletions(-) create mode 100644 packages/app-website-builder/src/BaseEditor/defaultConfig/Content/Preview/NoFrontendConnected.tsx create mode 100644 packages/app-website-builder/src/BaseEditor/defaultConfig/Content/Preview/PreviewFrame.tsx create mode 100644 packages/app-website-builder/src/BaseEditor/defaultConfig/Content/Preview/usePreviewConnection.ts create mode 100644 packages/app-website-builder/src/BaseEditor/defaultConfig/Content/SampleFrontendBanner.tsx create mode 100644 packages/app-website-builder/src/BaseEditor/defaultConfig/Content/sampleFrontend.ts diff --git a/packages/app-website-builder/src/BaseEditor/defaultConfig/Content/ContentPreviewConfig.tsx b/packages/app-website-builder/src/BaseEditor/defaultConfig/Content/ContentPreviewConfig.tsx index 4654de3ff43..ca61eae7a8f 100644 --- a/packages/app-website-builder/src/BaseEditor/defaultConfig/Content/ContentPreviewConfig.tsx +++ b/packages/app-website-builder/src/BaseEditor/defaultConfig/Content/ContentPreviewConfig.tsx @@ -4,6 +4,7 @@ import { Breadcrumbs } from "./Breadcrumbs/index.js"; import { DocumentPreview } from "./Preview/DocumentPreview.js"; import { AddressBar } from "./AddressBar/AddressBar.js"; import { BreakpointSelector } from "./AddressBar/BreakpointSelector.js"; +import { SampleFrontendBanner } from "./SampleFrontendBanner.js"; export const ContentPreviewConfig = () => { const { Ui } = EditorConfig; @@ -16,6 +17,11 @@ export const ContentPreviewConfig = () => { element={} /> } /> + } + /> } /> void; + /** + * Rendered on top of the preview instead of the loader. Used to explain why the preview is + * empty, while keeping the iframe mounted so that a late connection still resolves on its own. + */ + overlay?: React.ReactNode; } export const Iframe = observer(({ url, timestamp, ...props }: IframeProps) => { @@ -49,14 +54,15 @@ export const Iframe = observer(({ url, timestamp, ...props }: IframeProps) => { return ( - {props.showLoading ? ( - - ) : null} + {props.overlay ?? + (props.showLoading ? ( + + ) : null)} {/* Content wrapper - sized by iframe content */}
void; +} + +const COPY: Record = { + unreachable: { + title: "No frontend detected", + description: + "The editor renders your pages inside your own frontend, and nothing is currently running at" + }, + unresponsive: { + title: "Frontend didn't connect", + description: + "Something is running, but it never connected to the editor. Make sure the Website Builder SDK is set up in the app running at" + } +}; + +export const NoFrontendConnected = ({ origin, status, onRetry }: NoFrontendConnectedProps) => { + const { previewDomain, setPreviewDomain } = usePreviewDomain(); + + const loadSampleFrontend = useCallback(() => { + setPreviewDomain(SAMPLE_FRONTEND_DOMAIN); + }, [setPreviewDomain]); + + const openInstructions = useCallback(() => { + window.open(FRONTEND_SETUP_DOCS_URL, "_blank", "noopener,noreferrer"); + }, []); + + const copy = COPY[status]; + + // No point in offering the sample frontend when it's the domain that just failed to connect. + const canLoadSampleFrontend = previewDomain !== SAMPLE_FRONTEND_DOMAIN; + + return ( +
+ + {copy.description} {origin}. + + } + actions={ + <> +
+ ); +}; diff --git a/packages/app-website-builder/src/BaseEditor/defaultConfig/Content/Preview/Preview.tsx b/packages/app-website-builder/src/BaseEditor/defaultConfig/Content/Preview/Preview.tsx index 2ae468ddafd..903e5c97df0 100644 --- a/packages/app-website-builder/src/BaseEditor/defaultConfig/Content/Preview/Preview.tsx +++ b/packages/app-website-builder/src/BaseEditor/defaultConfig/Content/Preview/Preview.tsx @@ -1,9 +1,8 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react"; -import type { Messenger } from "@webiny/website-builder-sdk"; +import React, { useEffect, useMemo, useState } from "react"; import { ViewportManager } from "@webiny/website-builder-sdk"; import { mouseTracker } from "@webiny/website-builder-sdk"; import { useDocumentEditor } from "~/DocumentEditor/index.js"; -import { Iframe } from "./Iframe.js"; +import { PreviewFrame } from "./PreviewFrame.js"; import { DropZoneManager } from "./DropZoneManager.js"; import { DropZoneManagerProvider } from "./DropZoneManagerProvider.js"; import { KeyboardShortcuts } from "./KeyboardShortcuts.js"; @@ -82,21 +81,20 @@ export const Preview = () => { }; }, []); - const onConnected = useCallback((messenger: Messenger) => { - previewEvents.onConnected(messenger); - }, []); - return ( <> {({ url }) => ( -