Skip to content

Commit 27a7cdc

Browse files
[WEB-5581] fix: resolve logo spinner hydration and theme loading issues (#8450)
- Fix hydration mismatch by lazy loading components that depend on theme - Ensure LogoSpinner renders with correct theme on initial load
1 parent 0c795e9 commit 27a7cdc

File tree

5 files changed

+43
-30
lines changed

5 files changed

+43
-30
lines changed

apps/web/app/provider.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { lazy, Suspense } from "react";
2-
import { useTheme, ThemeProvider } from "next-themes";
2+
import { useTheme } from "next-themes";
33
import { SWRConfig } from "swr";
44
// Plane Imports
55
import { WEB_SWR_CONFIG } from "@plane/constants";
@@ -9,44 +9,45 @@ import { Toast } from "@plane/propel/toast";
99
import { resolveGeneralTheme } from "@plane/utils";
1010
// polyfills
1111
import "@/lib/polyfills";
12-
// progress bar
13-
import { AppProgressBar } from "@/lib/b-progress";
1412
// mobx store provider
1513
import { StoreProvider } from "@/lib/store-context";
16-
// wrappers
17-
import { InstanceWrapper } from "@/lib/wrappers/instance-wrapper";
1814

1915
// lazy imports
16+
const AppProgressBar = lazy(function AppProgressBar() {
17+
return import("@/lib/b-progress/AppProgressBar");
18+
});
19+
2020
const StoreWrapper = lazy(function StoreWrapper() {
2121
return import("@/lib/wrappers/store-wrapper");
2222
});
2323

24-
const PostHogProvider = lazy(function PostHogProvider() {
25-
return import("@/lib/posthog-provider");
24+
const InstanceWrapper = lazy(function InstanceWrapper() {
25+
return import("@/lib/wrappers/instance-wrapper");
2626
});
2727

2828
const ChatSupportModal = lazy(function ChatSupportModal() {
2929
return import("@/components/global/chat-support-modal");
3030
});
3131

32+
const PostHogProvider = lazy(function PostHogProvider() {
33+
return import("@/lib/posthog-provider");
34+
});
35+
3236
export interface IAppProvider {
3337
children: React.ReactNode;
3438
}
3539

36-
function ToastWithTheme() {
37-
const { resolvedTheme } = useTheme();
38-
return <Toast theme={resolveGeneralTheme(resolvedTheme)} />;
39-
}
40-
4140
export function AppProvider(props: IAppProvider) {
4241
const { children } = props;
4342
// themes
43+
const { resolvedTheme } = useTheme();
44+
4445
return (
4546
<StoreProvider>
46-
<ThemeProvider themes={["light", "dark", "light-contrast", "dark-contrast", "custom"]} defaultTheme="system">
47+
<>
4748
<AppProgressBar />
4849
<TranslationProvider>
49-
<ToastWithTheme />
50+
<Toast theme={resolveGeneralTheme(resolvedTheme)} />
5051
<StoreWrapper>
5152
<InstanceWrapper>
5253
<Suspense>
@@ -58,7 +59,7 @@ export function AppProvider(props: IAppProvider) {
5859
</InstanceWrapper>
5960
</StoreWrapper>
6061
</TranslationProvider>
61-
</ThemeProvider>
62+
</>
6263
</StoreProvider>
6364
);
6465
}

apps/web/app/root.tsx

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as Sentry from "@sentry/react-router";
33
import Script from "next/script";
44
import { Links, Meta, Outlet, Scripts } from "react-router";
55
import type { LinksFunction } from "react-router";
6+
import { ThemeProvider, useTheme } from "next-themes";
67
// plane imports
78
import { SITE_DESCRIPTION, SITE_NAME } from "@plane/constants";
89
import { cn } from "@plane/utils";
@@ -14,9 +15,10 @@ import faviconIco from "@/app/assets/favicon/favicon.ico?url";
1415
import icon180 from "@/app/assets/icons/icon-180x180.png?url";
1516
import icon512 from "@/app/assets/icons/icon-512x512.png?url";
1617
import ogImage from "@/app/assets/og-image.png?url";
17-
import { LogoSpinner } from "@/components/common/logo-spinner";
1818
import globalStyles from "@/styles/globals.css?url";
1919
import type { Route } from "./+types/root";
20+
// components
21+
import { LogoSpinner } from "@/components/common/logo-spinner";
2022
// local
2123
import { CustomErrorComponent } from "./error";
2224
import { AppProvider } from "./provider";
@@ -51,7 +53,7 @@ export function Layout({ children }: { children: ReactNode }) {
5153
const isSessionRecorderEnabled = parseInt(process.env.VITE_ENABLE_SESSION_RECORDER || "0");
5254

5355
return (
54-
<html lang="en">
56+
<html lang="en" suppressHydrationWarning>
5557
<head>
5658
<meta charSet="utf-8" />
5759
<meta name="viewport" content="width=device-width, initial-scale=1" />
@@ -66,16 +68,12 @@ export function Layout({ children }: { children: ReactNode }) {
6668
<Meta />
6769
<Links />
6870
</head>
69-
<body>
71+
<body suppressHydrationWarning>
7072
<div id="context-menu-portal" />
7173
<div id="editor-portal" />
72-
<AppProvider>
73-
<div
74-
className={cn("h-screen w-full overflow-hidden bg-canvas relative flex flex-col", "desktop-app-container")}
75-
>
76-
<main className="w-full h-full overflow-hidden relative">{children}</main>
77-
</div>
78-
</AppProvider>
74+
<ThemeProvider themes={["light", "dark", "light-contrast", "dark-contrast", "custom"]} defaultTheme="system">
75+
{children}
76+
</ThemeProvider>
7977
<Scripts />
8078
{!!isSessionRecorderEnabled && process.env.VITE_SESSION_RECORDER_KEY && (
8179
<Script id="clarity-tracking">
@@ -118,12 +116,25 @@ export const meta: Route.MetaFunction = () => [
118116
];
119117

120118
export default function Root() {
121-
return <Outlet />;
119+
return (
120+
<AppProvider>
121+
<div className={cn("h-screen w-full overflow-hidden bg-canvas relative flex flex-col", "desktop-app-container")}>
122+
<main className="w-full h-full overflow-hidden relative">
123+
<Outlet />
124+
</main>
125+
</div>
126+
</AppProvider>
127+
);
122128
}
123129

124130
export function HydrateFallback() {
131+
const { resolvedTheme } = useTheme();
132+
133+
// if we are on the server or the theme is not resolved, return an empty div
134+
if (typeof window === "undefined" || resolvedTheme === undefined) return <div />;
135+
125136
return (
126-
<div className="relative flex h-screen w-full items-center justify-center">
137+
<div className="relative flex bg-canvas h-screen w-full items-center justify-center">
127138
<LogoSpinner />
128139
</div>
129140
);

apps/web/core/lib/b-progress/AppProgressBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const PROGRESS_CONFIG: Readonly<ProgressConfig> = {
6060
* }
6161
* ```
6262
*/
63-
export function AppProgressBar(): null {
63+
export default function AppProgressBar(): null {
6464
const navigation = useNavigation();
6565
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
6666
const startedRef = useRef<boolean>(false);

apps/web/core/lib/b-progress/index.tsx

Lines changed: 0 additions & 1 deletion
This file was deleted.

apps/web/core/lib/wrappers/instance-wrapper.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type TInstanceWrapper = {
1111
children: ReactNode;
1212
};
1313

14-
export const InstanceWrapper = observer(function InstanceWrapper(props: TInstanceWrapper) {
14+
const InstanceWrapper = observer(function InstanceWrapper(props: TInstanceWrapper) {
1515
const { children } = props;
1616
// store
1717
const { isLoading, instance, error, fetchInstanceInfo } = useInstance();
@@ -40,3 +40,5 @@ export const InstanceWrapper = observer(function InstanceWrapper(props: TInstanc
4040

4141
return <>{children}</>;
4242
});
43+
44+
export default InstanceWrapper;

0 commit comments

Comments
 (0)