diff --git a/apps/studio/components/interfaces/App/ShellFallback.tsx b/apps/studio/components/interfaces/App/ShellFallback.tsx index ff6a1666af7..24722a22998 100644 --- a/apps/studio/components/interfaces/App/ShellFallback.tsx +++ b/apps/studio/components/interfaces/App/ShellFallback.tsx @@ -1,5 +1,10 @@ import { LogoLoader } from 'ui' +import { + BOOT_FALLBACK_MESSAGE, + BOOT_FALLBACK_REVEAL_DELAY_SECONDS, + BOOT_FALLBACK_SUPPORT_EMAIL, +} from '@/lib/boot-fallback-copy' import { IS_PLATFORM } from '@/lib/constants' // Baked into the prerendered SPA shell (_shell.html) — this is the static HTML @@ -15,7 +20,7 @@ export function ShellFallback() { #studio-shell-help { visibility: hidden; opacity: 0; - animation: studio-shell-help-reveal 0.3s ease-out 7s forwards; + animation: studio-shell-help-reveal 0.3s ease-out ${BOOT_FALLBACK_REVEAL_DELAY_SECONDS}s forwards; } @keyframes studio-shell-help-reveal { to { @@ -40,13 +45,13 @@ export function ShellFallback() { data-nosnippet="" className="max-w-md text-center text-sm text-foreground-light" > - Taking longer than expected? Try clearing your browser cookies and reloading the page. + {BOOT_FALLBACK_MESSAGE} {IS_PLATFORM && ( <> {' '} If the problem persists, contact{' '} - - support@supabase.com + + {BOOT_FALLBACK_SUPPORT_EMAIL} . diff --git a/apps/studio/components/ui/BootTimeoutFallback/BootTimeoutFallback.tsx b/apps/studio/components/ui/BootTimeoutFallback/BootTimeoutFallback.tsx new file mode 100644 index 00000000000..5ac98fe7087 --- /dev/null +++ b/apps/studio/components/ui/BootTimeoutFallback/BootTimeoutFallback.tsx @@ -0,0 +1,75 @@ +import { + BOOT_FALLBACK_MESSAGE, + BOOT_FALLBACK_REVEAL_DELAY_SECONDS, + BOOT_FALLBACK_SUPPORT_EMAIL, +} from '@/lib/boot-fallback-copy' +import { IS_PLATFORM } from '@/lib/constants' + +const FALLBACK_ID = 'studio-boot-fallback' + +// Next.js equivalent of the TanStack build's ShellFallback (see +// components/interfaces/App/ShellFallback.tsx): a safety net for a stuck +// chunk load (FE-4460). Next always server-renders real page content into +//
, so — unlike TanStack's ClientOnly fallback, which shows nothing +// until hydration — this can't be a full-screen overlay shown by default; it +// has to reveal itself only once it's clear the page is actually stuck. +// +// The reveal is pure CSS (a delayed animation), not a JS timer, so it still +// fires even if every JS chunk fails to load. Hiding it on a successful +// mount necessarily needs JS — see `clearBootTimeoutFallback` — but a +// missed hide is harmless: the reveal only starts after +// BOOT_FALLBACK_REVEAL_DELAY_SECONDS, by which point a successful mount has +// long since hidden it. +export function BootTimeoutFallback() { + return ( + <> + +
+

+ {BOOT_FALLBACK_MESSAGE} + {IS_PLATFORM && ( + <> + {' '} + If the problem persists, contact{' '} + {BOOT_FALLBACK_SUPPORT_EMAIL}. + + )} +

+
+ + ) +} + +/** Call once the app has actually mounted, to hide the fallback above. */ +export function clearBootTimeoutFallback() { + const el = document.getElementById(FALLBACK_ID) + if (el) el.style.display = 'none' +} diff --git a/apps/studio/lib/boot-fallback-copy.ts b/apps/studio/lib/boot-fallback-copy.ts new file mode 100644 index 00000000000..8f49994fe9b --- /dev/null +++ b/apps/studio/lib/boot-fallback-copy.ts @@ -0,0 +1,6 @@ +// Shared between ShellFallback (TanStack) and BootTimeoutFallback (Next.js) — +// same "stuck loading" message, different markup per runtime (see FE-4460). +export const BOOT_FALLBACK_REVEAL_DELAY_SECONDS = 7 +export const BOOT_FALLBACK_SUPPORT_EMAIL = 'support@supabase.io' +export const BOOT_FALLBACK_MESSAGE = + 'Taking longer than expected? Try clearing your browser cookies and reloading the page.' diff --git a/apps/studio/pages/_app.tsx b/apps/studio/pages/_app.tsx index 5d913d89585..576baa72ac1 100644 --- a/apps/studio/pages/_app.tsx +++ b/apps/studio/pages/_app.tsx @@ -48,6 +48,7 @@ import { MonacoThemeProvider } from '@/components/interfaces/App/MonacoThemeProv import { RouteValidationWrapper } from '@/components/interfaces/App/RouteValidationWrapper' import { MainScrollContainerProvider } from '@/components/layouts/MainScrollContainerContext' import { BannerStackProvider } from '@/components/ui/BannerStack/BannerStackProvider' +import { clearBootTimeoutFallback } from '@/components/ui/BootTimeoutFallback/BootTimeoutFallback' import { GlobalErrorBoundaryState } from '@/components/ui/ErrorBoundary/GlobalErrorBoundaryState' import { GlobalShortcuts } from '@/components/ui/GlobalShortcuts/GlobalShortcuts' import { getCLIReleaseVersion } from '@/data/misc/cli-release-version-query' @@ -157,6 +158,10 @@ function CustomApp({ Component, pageProps }: AppPropsWithLayout) { useThemeSandbox() + useEffect(() => { + clearBootTimeoutFallback() + }, []) + const isTestEnv = process.env.NEXT_PUBLIC_NODE_ENV === 'test' // [Joshen] Should target hosted staging, local dev, and local CLI only diff --git a/apps/studio/pages/_document.tsx b/apps/studio/pages/_document.tsx index 8560136df0f..9edaf781a02 100644 --- a/apps/studio/pages/_document.tsx +++ b/apps/studio/pages/_document.tsx @@ -1,5 +1,6 @@ import Document, { DocumentContext, Head, Html, Main, NextScript } from 'next/document' +import { BootTimeoutFallback } from '@/components/ui/BootTimeoutFallback/BootTimeoutFallback' import { inter, manrope, sourceCodePro } from '@/fonts' class MyDocument extends Document { @@ -14,6 +15,7 @@ class MyDocument extends Document { +