Compare commits

...

9 Commits

Author SHA1 Message Date
ONLY-yours 2149e9593a test: change all desktop router to dynamic import elements 2025-11-19 21:30:43 +08:00
ONLY-yours 543ef2ef38 test: test use dynamic import to replace lazy import 2025-11-19 21:24:20 +08:00
ONLY-yours 0aa757118e fix: add lazy import back 2025-11-19 21:14:23 +08:00
ONLY-yours 6c1199c93b test: test main layout back 2025-11-19 21:00:31 +08:00
ONLY-yours 580ed74fe7 test: test the render 2025-11-19 20:24:58 +08:00
ONLY-yours 3e847ddb01 test: test the componets 2025-11-19 20:21:07 +08:00
ONLY-yours 11a11a6d20 fix: test router hiden chilren 2025-11-19 20:09:44 +08:00
ONLY-yours 387337d1e1 fix: remove the root layout to test hydrate 2025-11-19 19:53:42 +08:00
ONLY-yours 95f81ec3f5 fix: fixed the hydrated error problem 2025-11-19 19:11:27 +08:00
10 changed files with 403 additions and 239 deletions
+11 -1
View File
@@ -1,6 +1,6 @@
'use client';
import { memo } from 'react';
import { memo, useEffect, useState } from 'react';
import { DesktopWorkspace, MobileWorkspace } from './components/WorkspaceLayout';
import TelemetryNotification from './components/features/TelemetryNotification';
@@ -17,6 +17,16 @@ const MobileChatPage = memo(() => {
});
const DesktopChatPage = memo(() => {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
console.log('DesktopChatPage');
setIsMounted(true);
}, []);
if (!isMounted) return null;
return (
<>
<PageTitle />
@@ -24,6 +24,7 @@ const CloudBanner = dynamic(() => import('@/features/AlertBanner/CloudBanner'));
const Layout = memo((props: { locale: Locales }) => {
const { locale } = props;
const { isPWA } = usePlatform();
const theme = useTheme();
@@ -0,0 +1,81 @@
'use client';
import { memo, Suspense, useMemo } from 'react';
import { RouterProvider } from 'react-router-dom';
import type { Locales } from '@/types/locale';
import { createDesktopRouter } from './desktopRouter.config';
interface ClientRouterProps {
locale: Locales;
}
/**
* Pure CSR Loading Fallback Component
*
* This component is displayed during:
* - Initial router data loading
* - Route transitions with loaders
* - Any async route-level operations
*
* NOTE: This runs ONLY on the client, never during SSR
*/
const RouterLoadingFallback = () => (
<div
style={{
alignItems: 'center',
display: 'flex',
height: '100vh',
justifyContent: 'center',
width: '100vw',
}}
>
<div style={{ textAlign: 'center' }}>
<div
style={{
animation: 'spin 1s linear infinite',
border: '4px solid #f3f3f3',
borderRadius: '50%',
borderTop: '4px solid #3498db',
height: '40px',
margin: '0 auto 16px',
width: '40px',
}}
/>
<p style={{ color: '#666', fontSize: '14px' }}>Loading...</p>
</div>
<style>{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}</style>
</div>
);
/**
* Desktop Client Router Component
*
* Pure CSR (Client-Side Rendering) implementation:
* - Wrapped with Next.js dynamic import (ssr: false) to prevent SSR
* - Uses React Suspense for client-side loading states
* - Router instance is memoized based on locale
* - All route loaders execute ONLY in the browser
*
* This component uses ReactDOM.createRoot semantics (via Next.js dynamic import),
* NOT hydration. There is no server-rendered HTML to match against.
*/
const DesktopClientRouter = memo<ClientRouterProps>(({ locale }) => {
const router = useMemo(() => createDesktopRouter(locale), [locale]);
return (
<Suspense fallback={<RouterLoadingFallback />}>
<RouterProvider router={router} />
</Suspense>
);
});
DesktopClientRouter.displayName = 'DesktopClientRouter';
export default DesktopClientRouter;
+21 -23
View File
@@ -1,38 +1,36 @@
'use client';
import dynamic from 'next/dynamic';
import { memo, useMemo } from 'react';
import { RouterProvider } from 'react-router-dom';
import BootErrorBoundary from '@/components/BootErrorBoundary';
import Loading from '@/components/Loading/BrandTextLoading';
import type { Locales } from '@/types/locale';
import { createDesktopRouter } from './desktopRouter.config';
interface ClientRouterProps {
locale: Locales;
}
const ClientRouter = memo<ClientRouterProps>(({ locale }) => {
const router = useMemo(() => createDesktopRouter(locale), [locale]);
return (
<BootErrorBoundary fallback={<Loading />}>
<RouterProvider router={router} />
</BootErrorBoundary>
);
});
ClientRouter.displayName = 'ClientRouter';
const DesktopRouterClient = dynamic(() => Promise.resolve(ClientRouter), {
loading: () => <Loading />,
/**
* Dynamic import with SSR disabled
*
* This creates a pure CSR boundary:
* - Server renders: nothing (null)
* - Client hydration: null (matches server)
* - After hydration: DesktopClientRouter mounts with ReactDOM.createRoot semantics
*
* The loading component is optional here since DesktopClientRouter
* has its own fallbackElement for route-level loading states.
*/
const DesktopRouterClient = dynamic(() => import('./DesktopClientRouter'), {
ssr: false,
});
interface DesktopRouterProps {
locale: Locales;
}
/**
* Desktop Router Wrapper
*
* This wrapper exists to:
* 1. Create a client component boundary for Next.js
* 2. Enable code splitting (Desktop bundle separated from Mobile)
* 3. Ensure the entire react-router-dom tree is client-only
*/
const DesktopRouter = ({ locale }: DesktopRouterProps) => {
return <DesktopRouterClient locale={locale} />;
};
+81
View File
@@ -0,0 +1,81 @@
'use client';
import { memo, Suspense, useMemo } from 'react';
import { RouterProvider } from 'react-router-dom';
import type { Locales } from '@/types/locale';
import { createMobileRouter } from './mobileRouter.config';
interface ClientRouterProps {
locale: Locales;
}
/**
* Pure CSR Loading Fallback Component
*
* This component is displayed during:
* - Initial router data loading
* - Route transitions with loaders
* - Any async route-level operations
*
* NOTE: This runs ONLY on the client, never during SSR
*/
const RouterLoadingFallback = () => (
<div
style={{
alignItems: 'center',
display: 'flex',
height: '100vh',
justifyContent: 'center',
width: '100vw',
}}
>
<div style={{ textAlign: 'center' }}>
<div
style={{
animation: 'spin 1s linear infinite',
border: '4px solid #f3f3f3',
borderRadius: '50%',
borderTop: '4px solid #3498db',
height: '40px',
margin: '0 auto 16px',
width: '40px',
}}
/>
<p style={{ color: '#666', fontSize: '14px' }}>Loading...</p>
</div>
<style>{`
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
`}</style>
</div>
);
/**
* Mobile Client Router Component
*
* Pure CSR (Client-Side Rendering) implementation:
* - Wrapped with Next.js dynamic import (ssr: false) to prevent SSR
* - Uses React Suspense for client-side loading states
* - Router instance is memoized based on locale
* - All route loaders execute ONLY in the browser
*
* This component uses ReactDOM.createRoot semantics (via Next.js dynamic import),
* NOT hydration. There is no server-rendered HTML to match against.
*/
const MobileClientRouter = memo<ClientRouterProps>(({ locale }) => {
const router = useMemo(() => createMobileRouter(locale), [locale]);
return (
<Suspense fallback={<RouterLoadingFallback />}>
<RouterProvider router={router} />
</Suspense>
);
});
MobileClientRouter.displayName = 'MobileClientRouter';
export default MobileClientRouter;
+21 -24
View File
@@ -1,39 +1,36 @@
'use client';
import dynamic from 'next/dynamic';
import { memo, useMemo } from 'react';
import { RouterProvider } from 'react-router-dom';
import BootErrorBoundary from '@/components/BootErrorBoundary';
import Loading from '@/components/Loading/BrandTextLoading';
import type { Locales } from '@/types/locale';
import { createMobileRouter } from './mobileRouter.config';
interface ClientRouterProps {
locale: Locales;
}
const ClientRouter = memo<ClientRouterProps>(({ locale }) => {
const router = useMemo(() => createMobileRouter(locale), [locale]);
return (
<BootErrorBoundary fallback={<Loading />}>
<RouterProvider router={router} />
</BootErrorBoundary>
);
});
ClientRouter.displayName = 'ClientRouter';
const MobileRouterClient = dynamic(() => Promise.resolve(ClientRouter), {
loading: () => <Loading />,
/**
* Dynamic import with SSR disabled
*
* This creates a pure CSR boundary:
* - Server renders: nothing (null)
* - Client hydration: null (matches server)
* - After hydration: MobileClientRouter mounts with ReactDOM.createRoot semantics
*
* The loading component is optional here since MobileClientRouter
* has its own fallbackElement for route-level loading states.
*/
const MobileRouterClient = dynamic(() => import('./MobileClientRouter'), {
ssr: false,
});
interface MobileRouterProps {
locale: Locales;
}
/**
* Mobile Router Wrapper
*
* This wrapper exists to:
* 1. Create a client component boundary for Next.js
* 2. Enable code splitting (Mobile bundle separated from Desktop)
* 3. Ensure the entire react-router-dom tree is client-only
*/
const MobileRouter = ({ locale }: MobileRouterProps) => {
return <MobileRouterClient locale={locale} />;
};
+171 -167
View File
@@ -1,74 +1,172 @@
'use client';
import { useEffect } from 'react';
import { type LoaderFunction, createBrowserRouter, redirect, useNavigate } from 'react-router-dom';
import dynamic from 'next/dynamic';
import { createBrowserRouter, redirect } from 'react-router-dom';
import Loading from '@/components/Loading/BrandTextLoading';
import { useGlobalStore } from '@/store/global';
import type { Locales } from '@/types/locale';
import DesktopMainLayout from './(main)/layouts/desktop';
import { idLoader, slugLoader } from './loaders/routeParams';
// Component to register navigate function in global store
const NavigatorRegistrar = () => {
const navigate = useNavigate();
/**
* Desktop Router Configuration - Pure CSR Mode
*
* IMPORTANT: This router runs ONLY in the browser (client-side).
*
* Key characteristics:
* - createBrowserRouter uses window.history API (client-only)
* - All loaders execute in the browser during navigation
* - No server-side rendering or hydration involved
* - Route data fetching happens on-demand during client navigation
*
* The entire router tree is wrapped with Next.js dynamic import (ssr: false),
* ensuring this code never executes on the server.
*/
useEffect(() => {
useGlobalStore.setState({ navigate });
// Chat components
const DesktopChatPage = dynamic(
() => import('./(main)/chat/index').then((m) => m.DesktopChatPage),
{ ssr: false },
);
const ChatLayout = dynamic(() => import('./(main)/chat/_layout/Desktop'), { ssr: false });
return () => {
useGlobalStore.setState({ navigate: undefined });
};
}, [navigate]);
// Discover List components
const DesktopHomePage = dynamic(
() => import('./(main)/discover/(list)/(home)/index').then((m) => m.DesktopHomePage),
{ ssr: false },
);
const DesktopAssistantPage = dynamic(
() => import('./(main)/discover/(list)/assistant/index').then((m) => m.DesktopAssistantPage),
{ ssr: false },
);
const DiscoverAssistantLayout = dynamic(
() => import('./(main)/discover/(list)/assistant/_layout/Desktop'),
{ ssr: false },
);
const DiscoverListMcpPage = dynamic(
() => import('./(main)/discover/(list)/mcp/index').then((m) => m.DesktopMcpPage),
{ ssr: false },
);
const DiscoverMcpLayout = dynamic(
() => import('./(main)/discover/(list)/mcp/_layout/Desktop'),
{ ssr: false },
);
const DiscoverListModelPage = dynamic(
() => import('./(main)/discover/(list)/model/index').then((m) => m.DesktopModelPage),
{ ssr: false },
);
const DiscoverModelLayout = dynamic(
() => import('./(main)/discover/(list)/model/_layout/Desktop'),
{ ssr: false },
);
const DiscoverListProviderPage = dynamic(
() => import('./(main)/discover/(list)/provider/index').then((m) => m.DesktopProviderPage),
{ ssr: false },
);
const DiscoverListLayout = dynamic(
() => import('./(main)/discover/(list)/_layout/Desktop/index'),
{ ssr: false },
);
return null;
};
// Discover Detail components
const DesktopDiscoverAssistantDetailPage = dynamic(
() =>
import('./(main)/discover/(detail)/assistant/index').then(
(m) => m.DesktopDiscoverAssistantDetailPage,
),
{ ssr: false },
);
const DiscoverDetailMcpPage = dynamic(
() => import('./(main)/discover/(detail)/mcp/index').then((m) => m.DesktopMcpPage),
{ ssr: false },
);
const DiscoverDetailModelPage = dynamic(
() => import('./(main)/discover/(detail)/model/index').then((m) => m.DesktopModelPage),
{ ssr: false },
);
const DiscoverDetailProviderPage = dynamic(
() => import('./(main)/discover/(detail)/provider/index').then((m) => m.DesktopProviderPage),
{ ssr: false },
);
const DiscoverDetailLayout = dynamic(
() => import('./(main)/discover/(detail)/_layout/Desktop'),
{ ssr: false },
);
const DiscoverLayout = dynamic(
() => import('./(main)/discover/_layout/Desktop/index'),
{ ssr: false },
);
// Root layout wrapper component - just registers navigator and renders outlet
// Note: Desktop layout is provided by individual route components
const RootLayout = (props: { locale: Locales }) => {
return (
<>
<NavigatorRegistrar />
<DesktopMainLayout locale={props.locale} />
</>
);
};
// Knowledge components
const KnowledgeHome = dynamic(() => import('./(main)/knowledge/routes/KnowledgeHome'), {
ssr: false,
});
const KnowledgeBasesList = dynamic(() => import('./(main)/knowledge/routes/KnowledgeBasesList'), {
ssr: false,
});
const KnowledgeBaseDetail = dynamic(
() => import('./(main)/knowledge/routes/KnowledgeBaseDetail'),
{ ssr: false },
);
const KnowledgeLayout = dynamic(() => import('./(main)/knowledge/_layout/Desktop'), {
ssr: false,
});
// Hydration gate loader -always return true to bypass hydration gate
const hydrationGateLoader: LoaderFunction = () => {
return true
};
// Settings components
const SettingsLayout = dynamic(() => import('./(main)/settings/_layout/Desktop'), { ssr: false });
const SettingsLayoutWrapper = dynamic(() => import('./(main)/settings/_layout/DesktopWrapper'), {
ssr: false,
});
// Image components
const ImagePage = dynamic(() => import('./(main)/image'), { ssr: false });
const ImageLayoutWrapper = dynamic(() => import('./(main)/image/_layout/DesktopWrapper'), {
ssr: false,
});
// Labs components
const LabsPage = dynamic(() => import('./(main)/labs'), { ssr: false });
// Profile components
const ProfileHomePage = dynamic(() => import('./(main)/profile/(home)/desktop'), { ssr: false });
const ProfileApikeyPage = dynamic(() => import('./(main)/profile/apikey/index'), { ssr: false });
const DesktopProfileSecurityPage = dynamic(
() => import('./(main)/profile/security/index').then((m) => m.DesktopProfileSecurityPage),
{ ssr: false },
);
const DesktopProfileStatsPage = dynamic(
() => import('./(main)/profile/stats/index').then((m) => m.DesktopProfileStatsPage),
{ ssr: false },
);
const DesktopProfileUsagePage = dynamic(
() => import('./(main)/profile/usage/index').then((m) => m.DesktopProfileUsagePage),
{ ssr: false },
);
const ProfileLayoutWrapper = dynamic(() => import('./(main)/profile/_layout/DesktopWrapper'), {
ssr: false,
});
// Root layout wrapper component
const RootLayout = (props: { locale: Locales }) => <DesktopMainLayout locale={props.locale} />;
// Create desktop router configuration
export const createDesktopRouter = (locale: Locales) =>
createBrowserRouter([
{
HydrateFallback: () => <Loading />,
children: [
// Chat routes
{
children: [
{
element: <DesktopChatPage />,
index: true,
lazy: () =>
import('./(main)/chat/index').then((m) => ({
Component: m.DesktopChatPage,
})),
},
{
lazy: () =>
import('./(main)/chat/index').then((m) => ({
Component: m.DesktopChatPage,
})),
element: <DesktopChatPage />,
path: '*',
},
],
lazy: () =>
import('./(main)/chat/_layout/Desktop').then((m) => ({
Component: m.default,
})),
element: <ChatLayout />,
path: 'chat',
},
@@ -81,117 +179,72 @@ export const createDesktopRouter = (locale: Locales) =>
{
children: [
{
element: <DesktopAssistantPage />,
index: true,
lazy: () =>
import('./(main)/discover/(list)/assistant/index').then((m) => ({
Component: m.DesktopAssistantPage,
})),
},
],
lazy: () =>
import('./(main)/discover/(list)/assistant/_layout/Desktop').then((m) => ({
Component: m.default,
})),
element: <DiscoverAssistantLayout />,
path: 'assistant',
},
{
children: [
{
element: <DiscoverListModelPage />,
index: true,
lazy: () =>
import('./(main)/discover/(list)/model/index').then((m) => ({
Component: m.DesktopModelPage,
})),
},
],
lazy: () =>
import('./(main)/discover/(list)/model/_layout/Desktop').then((m) => ({
Component: m.default,
})),
element: <DiscoverModelLayout />,
path: 'model',
},
{
lazy: () =>
import('./(main)/discover/(list)/provider/index').then((m) => ({
Component: m.DesktopProviderPage,
})),
element: <DiscoverListProviderPage />,
path: 'provider',
},
{
children: [
{
element: <DiscoverListMcpPage />,
index: true,
lazy: () =>
import('./(main)/discover/(list)/mcp/index').then((m) => ({
Component: m.DesktopMcpPage,
})),
},
],
lazy: () =>
import('./(main)/discover/(list)/mcp/_layout/Desktop').then((m) => ({
Component: m.default,
})),
element: <DiscoverMcpLayout />,
path: 'mcp',
},
{
element: <DesktopHomePage />,
index: true,
lazy: () =>
import('./(main)/discover/(list)/(home)/index').then((m) => ({
Component: m.DesktopHomePage,
})),
},
],
lazy: () =>
import('./(main)/discover/(list)/_layout/Desktop/index').then((m) => ({
Component: m.default,
})),
element: <DiscoverListLayout />,
},
// Detail routes (with DetailLayout)
{
children: [
{
lazy: () =>
import('./(main)/discover/(detail)/assistant/index').then((m) => ({
Component: m.DesktopDiscoverAssistantDetailPage,
})),
element: <DesktopDiscoverAssistantDetailPage />,
loader: slugLoader,
path: 'assistant/:slug',
},
{
lazy: () =>
import('./(main)/discover/(detail)/model/index').then((m) => ({
Component: m.DesktopModelPage,
})),
element: <DiscoverDetailModelPage />,
loader: slugLoader,
path: 'model/:slug',
},
{
lazy: () =>
import('./(main)/discover/(detail)/provider/index').then((m) => ({
Component: m.DesktopProviderPage,
})),
element: <DiscoverDetailProviderPage />,
loader: slugLoader,
path: 'provider/:slug',
},
{
lazy: () =>
import('./(main)/discover/(detail)/mcp/index').then((m) => ({
Component: m.DesktopMcpPage,
})),
element: <DiscoverDetailMcpPage />,
loader: slugLoader,
path: 'mcp/:slug',
},
],
lazy: () =>
import('./(main)/discover/(detail)/_layout/Desktop').then((m) => ({
Component: m.default,
})),
element: <DiscoverDetailLayout />,
},
],
lazy: () =>
import('./(main)/discover/_layout/Desktop/index').then((m) => ({
Component: m.default,
})),
element: <DiscoverLayout />,
path: 'discover',
},
@@ -199,40 +252,25 @@ export const createDesktopRouter = (locale: Locales) =>
{
children: [
{
element: <KnowledgeHome />,
index: true,
lazy: () =>
import('./(main)/knowledge/routes/KnowledgeHome').then((m) => ({
Component: m.default,
})),
},
{
lazy: () =>
import('./(main)/knowledge/routes/KnowledgeBasesList').then((m) => ({
Component: m.default,
})),
element: <KnowledgeBasesList />,
path: 'bases',
},
{
lazy: () =>
import('./(main)/knowledge/routes/KnowledgeBaseDetail').then((m) => ({
Component: m.default,
})),
element: <KnowledgeBaseDetail />,
loader: idLoader,
path: 'bases/:id',
},
{
lazy: () =>
import('./(main)/knowledge/routes/KnowledgeBaseDetail').then((m) => ({
Component: m.default,
})),
element: <KnowledgeBaseDetail />,
loader: idLoader,
path: '*',
},
],
lazy: () =>
import('./(main)/knowledge/_layout/Desktop').then((m) => ({
Component: m.default,
})),
element: <KnowledgeLayout />,
path: 'knowledge',
},
@@ -240,17 +278,11 @@ export const createDesktopRouter = (locale: Locales) =>
{
children: [
{
element: <SettingsLayout />,
index: true,
lazy: () =>
import('./(main)/settings/_layout/Desktop').then((m) => ({
Component: m.default,
})),
},
],
lazy: () =>
import('./(main)/settings/_layout/DesktopWrapper').then((m) => ({
Component: m.default,
})),
element: <SettingsLayoutWrapper />,
path: 'settings',
},
@@ -258,26 +290,17 @@ export const createDesktopRouter = (locale: Locales) =>
{
children: [
{
element: <ImagePage />,
index: true,
lazy: () =>
import('./(main)/image').then((m) => ({
Component: m.default,
})),
},
],
lazy: () =>
import('./(main)/image/_layout/DesktopWrapper').then((m) => ({
Component: m.default,
})),
element: <ImageLayoutWrapper />,
path: 'image',
},
// Labs routes
{
lazy: () =>
import('./(main)/labs').then((m) => ({
Component: m.default,
})),
element: <LabsPage />,
path: 'labs',
},
@@ -285,45 +308,27 @@ export const createDesktopRouter = (locale: Locales) =>
{
children: [
{
element: <ProfileHomePage />,
index: true,
lazy: () =>
import('./(main)/profile/(home)/desktop').then((m) => ({
Component: m.default,
})),
},
{
lazy: () =>
import('./(main)/profile/apikey/index').then((m) => ({
Component: m.default,
})),
element: <ProfileApikeyPage />,
path: 'apikey',
},
{
lazy: () =>
import('./(main)/profile/security/index').then((m) => ({
Component: m.DesktopProfileSecurityPage,
})),
element: <DesktopProfileSecurityPage />,
path: 'security',
},
{
lazy: () =>
import('./(main)/profile/stats/index').then((m) => ({
Component: m.DesktopProfileStatsPage,
})),
element: <DesktopProfileStatsPage />,
path: 'stats',
},
{
lazy: () =>
import('./(main)/profile/usage/index').then((m) => ({
Component: m.DesktopProfileUsagePage,
})),
element: <DesktopProfileUsagePage />,
path: 'usage',
},
],
lazy: () =>
import('./(main)/profile/_layout/DesktopWrapper').then((m) => ({
Component: m.default,
})),
element: <ProfileLayoutWrapper />,
path: 'profile',
},
@@ -340,7 +345,6 @@ export const createDesktopRouter = (locale: Locales) =>
},
],
element: <RootLayout locale={locale} />,
loader: hydrationGateLoader,
path: '/',
},
]);
+16 -8
View File
@@ -1,15 +1,29 @@
'use client';
import { useEffect } from 'react';
import { type LoaderFunction, createBrowserRouter, redirect, useNavigate } from 'react-router-dom';
import { createBrowserRouter, redirect, useNavigate } from 'react-router-dom';
import Loading from '@/components/Loading/BrandTextLoading';
import { useGlobalStore } from '@/store/global';
import type { Locales } from '@/types/locale';
import { MobileMainLayout } from './(main)/layouts/mobile';
import { idLoader, slugLoader } from './loaders/routeParams';
/**
* Mobile Router Configuration - Pure CSR Mode
*
* IMPORTANT: This router runs ONLY in the browser (client-side).
*
* Key characteristics:
* - createBrowserRouter uses window.history API (client-only)
* - All loaders execute in the browser during navigation
* - No server-side rendering or hydration involved
* - Route data fetching happens on-demand during client navigation
*
* The entire router tree is wrapped with Next.js dynamic import (ssr: false),
* ensuring this code never executes on the server.
*/
// Component to register navigate function in global store
const NavigatorRegistrar = () => {
const navigate = useNavigate();
@@ -34,16 +48,11 @@ const RootLayout = (props: { locale: Locales }) => (
</>
);
// Hydration gate loader -always return true to bypass hydration gate
const hydrationGateLoader: LoaderFunction = () => {
return true
};
// Create mobile router configuration
export const createMobileRouter = (locale: Locales) =>
createBrowserRouter([
{
HydrateFallback: () => <Loading />,
children: [
// Chat routes
{
@@ -372,7 +381,6 @@ export const createMobileRouter = (locale: Locales) =>
},
],
element: <RootLayout locale={locale} />,
loader: hydrationGateLoader,
path: '/',
},
]);
@@ -63,16 +63,7 @@ const StoreInitialization = memo(() => {
// init user state
useInitUserState(isLoginOnInit, serverConfig, {
onError: () => {
// 即使失败也要设置标志,避免应用卡住
useGlobalStore.setState({ isAppHydrated: true });
console.warn('[Hydration] Client state initialization failed.');
},
onSuccess: (state) => {
// 设置水合完成标志
useGlobalStore.setState({ isAppHydrated: true });
console.log('[Hydration] Client state initialized successfully.');
if (state.isOnboard === false) {
router.push('/onboard');
}
-7
View File
@@ -122,12 +122,6 @@ export interface GlobalState {
* 启动时为 Idle,完成为 Ready,报错为 Error
*/
initClientDBStage: DatabaseLoadingState;
/**
* 应用水合状态标志
* 用于指示客户端状态是否已从 StoreInitialization 完成加载
* 默认为 falseStoreInitialization 完成后设置为 true
*/
isAppHydrated: boolean;
isMobile?: boolean;
isStatusInit?: boolean;
latestVersion?: string;
@@ -170,7 +164,6 @@ export const INITIAL_STATUS = {
export const initialState: GlobalState = {
initClientDBStage: DatabaseLoadingState.Idle,
isAppHydrated: false,
isMobile: false,
isStatusInit: false,
sidebarKey: SidebarTabKey.Chat,