mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-31 14:09:42 +07:00
🐛 fix: fixed the hydrated false problem (#10308)
* fix: fixed the hydrated error problem * fix: use next/dynamic to replace react-router-dom lazy import * fix: add registor NavigatorRegistrar back * fix: add dynamic loading components * fix: change the dynamic config * fix: add losting loading layout * fix: delete useless memo * fix: add ErrorBoundary in some layout
This commit is contained in:
20
src/app/[variants]/DesktopClientRouter.tsx
Normal file
20
src/app/[variants]/DesktopClientRouter.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
'use client';
|
||||
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
|
||||
import type { Locales } from '@/types/locale';
|
||||
|
||||
import { createDesktopRouter } from './desktopRouter.config';
|
||||
|
||||
interface ClientRouterProps {
|
||||
locale: Locales;
|
||||
}
|
||||
|
||||
const ClientRouter = ({ locale }: ClientRouterProps) => {
|
||||
const router = createDesktopRouter(locale);
|
||||
return <RouterProvider router={router} />;
|
||||
};
|
||||
|
||||
ClientRouter.displayName = 'ClientRouter';
|
||||
|
||||
export default ClientRouter;
|
||||
@@ -1,31 +1,11 @@
|
||||
'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), {
|
||||
const DesktopRouterClient = dynamic(() => import('./DesktopClientRouter'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
20
src/app/[variants]/MobileClientRouter.tsx
Normal file
20
src/app/[variants]/MobileClientRouter.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
'use client';
|
||||
|
||||
import { RouterProvider } from 'react-router-dom';
|
||||
|
||||
import type { Locales } from '@/types/locale';
|
||||
|
||||
import { createMobileRouter } from './mobileRouter.config';
|
||||
|
||||
interface ClientRouterProps {
|
||||
locale: Locales;
|
||||
}
|
||||
|
||||
const ClientRouter = ({ locale }: ClientRouterProps) => {
|
||||
const router = createMobileRouter(locale);
|
||||
return <RouterProvider router={router} />;
|
||||
};
|
||||
|
||||
ClientRouter.displayName = 'ClientRouter';
|
||||
|
||||
export default ClientRouter;
|
||||
@@ -1,32 +1,11 @@
|
||||
'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), {
|
||||
const MobileRouterClient = dynamic(() => import('./MobileClientRouter'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useEffect } from 'react';
|
||||
import { type LoaderFunction, createBrowserRouter, redirect, useNavigate } from 'react-router-dom';
|
||||
import { createBrowserRouter, redirect, useNavigate, useRouteError } from 'react-router-dom';
|
||||
|
||||
import ErrorCapture from '@/components/Error';
|
||||
import Loading from '@/components/Loading/BrandTextLoading';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import type { Locales } from '@/types/locale';
|
||||
@@ -10,6 +12,213 @@ import type { Locales } from '@/types/locale';
|
||||
import DesktopMainLayout from './(main)/layouts/desktop';
|
||||
import { idLoader, slugLoader } from './loaders/routeParams';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// Chat components
|
||||
const DesktopChatPage = dynamic(
|
||||
() => import('./(main)/chat/index').then((m) => m.DesktopChatPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const ChatLayout = dynamic(() => import('./(main)/chat/_layout/Desktop'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Discover List components
|
||||
const DesktopHomePage = dynamic(
|
||||
() => import('./(main)/discover/(list)/(home)/index').then((m) => m.DesktopHomePage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DesktopAssistantPage = dynamic(
|
||||
() => import('./(main)/discover/(list)/assistant/index').then((m) => m.DesktopAssistantPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverAssistantLayout = dynamic(
|
||||
() => import('./(main)/discover/(list)/assistant/_layout/Desktop'),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverListMcpPage = dynamic(
|
||||
() => import('./(main)/discover/(list)/mcp/index').then((m) => m.DesktopMcpPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverMcpLayout = dynamic(() => import('./(main)/discover/(list)/mcp/_layout/Desktop'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const DiscoverListModelPage = dynamic(
|
||||
() => import('./(main)/discover/(list)/model/index').then((m) => m.DesktopModelPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverModelLayout = dynamic(
|
||||
() => import('./(main)/discover/(list)/model/_layout/Desktop'),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverListProviderPage = dynamic(
|
||||
() => import('./(main)/discover/(list)/provider/index').then((m) => m.DesktopProviderPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverListLayout = dynamic(() => import('./(main)/discover/(list)/_layout/Desktop/index'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Discover Detail components
|
||||
const DesktopDiscoverAssistantDetailPage = dynamic(
|
||||
() =>
|
||||
import('./(main)/discover/(detail)/assistant/index').then(
|
||||
(m) => m.DesktopDiscoverAssistantDetailPage,
|
||||
),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverDetailMcpPage = dynamic(
|
||||
() => import('./(main)/discover/(detail)/mcp/index').then((m) => m.DesktopMcpPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverDetailModelPage = dynamic(
|
||||
() => import('./(main)/discover/(detail)/model/index').then((m) => m.DesktopModelPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverDetailProviderPage = dynamic(
|
||||
() => import('./(main)/discover/(detail)/provider/index').then((m) => m.DesktopProviderPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverDetailLayout = dynamic(() => import('./(main)/discover/(detail)/_layout/Desktop'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const DiscoverLayout = dynamic(() => import('./(main)/discover/_layout/Desktop/index'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Knowledge components
|
||||
const KnowledgeHome = dynamic(() => import('./(main)/knowledge/routes/KnowledgeHome'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const KnowledgeBasesList = dynamic(() => import('./(main)/knowledge/routes/KnowledgeBasesList'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const KnowledgeBaseDetail = dynamic(() => import('./(main)/knowledge/routes/KnowledgeBaseDetail'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const KnowledgeLayout = dynamic(() => import('./(main)/knowledge/_layout/Desktop'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Settings components
|
||||
const SettingsLayout = dynamic(() => import('./(main)/settings/_layout/Desktop'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const SettingsLayoutWrapper = dynamic(() => import('./(main)/settings/_layout/DesktopWrapper'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Image components
|
||||
const ImagePage = dynamic(() => import('./(main)/image'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const ImageLayoutWrapper = dynamic(() => import('./(main)/image/_layout/DesktopWrapper'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Labs components
|
||||
const LabsPage = dynamic(() => import('./(main)/labs'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Profile components
|
||||
const ProfileHomePage = dynamic(() => import('./(main)/profile/(home)/desktop'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const ProfileApikeyPage = dynamic(() => import('./(main)/profile/apikey/index'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const DesktopProfileSecurityPage = dynamic(
|
||||
() => import('./(main)/profile/security/index').then((m) => m.DesktopProfileSecurityPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DesktopProfileStatsPage = dynamic(
|
||||
() => import('./(main)/profile/stats/index').then((m) => m.DesktopProfileStatsPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DesktopProfileUsagePage = dynamic(
|
||||
() => import('./(main)/profile/usage/index').then((m) => m.DesktopProfileUsagePage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const ProfileLayoutWrapper = dynamic(() => import('./(main)/profile/_layout/DesktopWrapper'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Component to register navigate function in global store
|
||||
const NavigatorRegistrar = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -25,50 +234,57 @@ const NavigatorRegistrar = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
// 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} />
|
||||
</>
|
||||
);
|
||||
// Error boundary factory for React Router errorElement
|
||||
const createErrorBoundary = (resetPath: string) => {
|
||||
const ErrorBoundary = () => {
|
||||
const error = useRouteError() as Error;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const reset = () => {
|
||||
navigate(resetPath);
|
||||
};
|
||||
|
||||
return <ErrorCapture error={error} reset={reset} />;
|
||||
};
|
||||
return ErrorBoundary;
|
||||
};
|
||||
|
||||
// Hydration gate loader -always return true to bypass hydration gate
|
||||
const hydrationGateLoader: LoaderFunction = () => {
|
||||
return true
|
||||
};
|
||||
// Create error boundaries for each route
|
||||
const ChatErrorBoundary = createErrorBoundary('/chat');
|
||||
const DiscoverErrorBoundary = createErrorBoundary('/discover');
|
||||
const KnowledgeErrorBoundary = createErrorBoundary('/knowledge');
|
||||
const SettingsErrorBoundary = createErrorBoundary('/settings');
|
||||
const ImageErrorBoundary = createErrorBoundary('/image');
|
||||
const ProfileErrorBoundary = createErrorBoundary('/profile');
|
||||
const RootErrorBoundary = createErrorBoundary('/chat'); // Root level falls back to chat
|
||||
|
||||
// Root layout wrapper component
|
||||
const RootLayout = (props: { locale: Locales }) => (
|
||||
<>
|
||||
<NavigatorRegistrar />
|
||||
<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 />,
|
||||
errorElement: <ChatErrorBoundary />,
|
||||
path: 'chat',
|
||||
},
|
||||
|
||||
@@ -81,117 +297,73 @@ 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 />,
|
||||
errorElement: <DiscoverErrorBoundary />,
|
||||
path: 'discover',
|
||||
},
|
||||
|
||||
@@ -199,40 +371,26 @@ 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 />,
|
||||
errorElement: <KnowledgeErrorBoundary />,
|
||||
path: 'knowledge',
|
||||
},
|
||||
|
||||
@@ -240,17 +398,12 @@ 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 />,
|
||||
errorElement: <SettingsErrorBoundary />,
|
||||
path: 'settings',
|
||||
},
|
||||
|
||||
@@ -258,26 +411,18 @@ 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 />,
|
||||
errorElement: <ImageErrorBoundary />,
|
||||
path: 'image',
|
||||
},
|
||||
|
||||
// Labs routes
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/labs').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <LabsPage />,
|
||||
path: 'labs',
|
||||
},
|
||||
|
||||
@@ -285,45 +430,28 @@ 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 />,
|
||||
errorElement: <ProfileErrorBoundary />,
|
||||
path: 'profile',
|
||||
},
|
||||
|
||||
@@ -340,7 +468,7 @@ export const createDesktopRouter = (locale: Locales) =>
|
||||
},
|
||||
],
|
||||
element: <RootLayout locale={locale} />,
|
||||
loader: hydrationGateLoader,
|
||||
errorElement: <RootErrorBoundary />,
|
||||
path: '/',
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useEffect } from 'react';
|
||||
import { type LoaderFunction, createBrowserRouter, redirect, useNavigate } from 'react-router-dom';
|
||||
import { createBrowserRouter, redirect, useNavigate, useRouteError } from 'react-router-dom';
|
||||
|
||||
import ErrorCapture from '@/components/Error';
|
||||
import Loading from '@/components/Loading/BrandTextLoading';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import type { Locales } from '@/types/locale';
|
||||
@@ -10,6 +12,233 @@ 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.
|
||||
*/
|
||||
|
||||
// Chat components
|
||||
const MobileChatPage = dynamic(() => import('./(main)/chat/index').then((m) => m.MobileChatPage), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const ChatSettings = dynamic(() => import('./(main)/chat/settings'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const ChatLayout = dynamic(() => import('./(main)/chat/_layout/Mobile'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Discover List components
|
||||
const MobileHomePage = dynamic(
|
||||
() => import('./(main)/discover/(list)/(home)/index').then((m) => m.MobileHomePage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const MobileAssistantPage = dynamic(
|
||||
() => import('./(main)/discover/(list)/assistant/index').then((m) => m.MobileAssistantPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverAssistantLayout = dynamic(
|
||||
() => import('./(main)/discover/(list)/assistant/_layout/Mobile'),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverListMobileModelPage = dynamic(
|
||||
() => import('./(main)/discover/(list)/model/index').then((m) => m.MobileModelPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverModelLayout = dynamic(() => import('./(main)/discover/(list)/model/_layout/Mobile'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const DiscoverListMobileProviderPage = dynamic(
|
||||
() => import('./(main)/discover/(list)/provider/index').then((m) => m.MobileProviderPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverListMobileMcpPage = dynamic(
|
||||
() => import('./(main)/discover/(list)/mcp/index').then((m) => m.MobileMcpPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverMcpLayout = dynamic(() => import('./(main)/discover/(list)/mcp/_layout/Mobile'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const DiscoverListLayout = dynamic(() => import('./(main)/discover/(list)/_layout/Mobile/index'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Discover Detail components
|
||||
const MobileDiscoverAssistantDetailPage = dynamic(
|
||||
() =>
|
||||
import('./(main)/discover/(detail)/assistant/index').then(
|
||||
(m) => m.MobileDiscoverAssistantDetailPage,
|
||||
),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverDetailMobileModelPage = dynamic(
|
||||
() => import('./(main)/discover/(detail)/model/index').then((m) => m.MobileModelPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverDetailMobileProviderPage = dynamic(
|
||||
() => import('./(main)/discover/(detail)/provider/index').then((m) => m.MobileProviderPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverDetailMobileMcpPage = dynamic(
|
||||
() => import('./(main)/discover/(detail)/mcp/index').then((m) => m.MobileMcpPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverDetailLayout = dynamic(
|
||||
() => import('./(main)/discover/(detail)/_layout/Mobile/index'),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const DiscoverLayout = dynamic(() => import('./(main)/discover/_layout/Mobile/index'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Knowledge components
|
||||
const KnowledgeHome = dynamic(() => import('./(main)/knowledge/routes/KnowledgeHome'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const KnowledgeBasesList = dynamic(() => import('./(main)/knowledge/routes/KnowledgeBasesList'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const KnowledgeBaseDetail = dynamic(() => import('./(main)/knowledge/routes/KnowledgeBaseDetail'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const KnowledgeLayout = dynamic(() => import('./(main)/knowledge/_layout/Mobile'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Settings components
|
||||
const SettingsLayout = dynamic(() => import('./(main)/settings/_layout/Mobile'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const SettingsLayoutWrapper = dynamic(() => import('./(main)/settings/_layout/MobileWrapper'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Image components
|
||||
const ImageComingSoon = dynamic(() => import('./(main)/image/ComingSoon'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const ImageLayoutMobile = dynamic(() => import('./(main)/image/_layout/Mobile'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Labs components
|
||||
const LabsPage = dynamic(() => import('./(main)/labs'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Profile components
|
||||
const ProfileHomePage = dynamic(() => import('./(main)/profile/(home)'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const ProfileApikeyPage = dynamic(() => import('./(main)/profile/apikey/index'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const MobileProfileSecurityPage = dynamic(
|
||||
() => import('./(main)/profile/security').then((m) => m.MobileProfileSecurityPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const MobileProfileStatsPage = dynamic(
|
||||
() => import('./(main)/profile/stats').then((m) => m.MobileProfileStatsPage),
|
||||
{
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
},
|
||||
);
|
||||
const ProfileLayoutMobile = dynamic(() => import('./(main)/profile/_layout/Mobile'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Me (mobile personal center) components
|
||||
const MeHomePage = dynamic(() => import('./(main)/(mobile)/me/(home)'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const MeHomeLayout = dynamic(() => import('./(main)/(mobile)/me/(home)/layout'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const MeProfilePage = dynamic(() => import('./(main)/(mobile)/me/profile'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const MeProfileLayout = dynamic(() => import('./(main)/(mobile)/me/profile/layout'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const MeSettingsPage = dynamic(() => import('./(main)/(mobile)/me/settings'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
const MeSettingsLayout = dynamic(() => import('./(main)/(mobile)/me/settings/layout'), {
|
||||
loading: () => <Loading />,
|
||||
ssr: false,
|
||||
});
|
||||
|
||||
// Component to register navigate function in global store
|
||||
const NavigatorRegistrar = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -25,8 +254,32 @@ const NavigatorRegistrar = () => {
|
||||
return null;
|
||||
};
|
||||
|
||||
// Root layout wrapper component - just registers navigator and renders outlet
|
||||
// Note: Mobile layout is provided by individual route components
|
||||
// Error boundary factory for React Router errorElement
|
||||
const createErrorBoundary = (resetPath: string) => {
|
||||
const ErrorBoundary = () => {
|
||||
const error = useRouteError() as Error;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const reset = () => {
|
||||
navigate(resetPath);
|
||||
};
|
||||
|
||||
return <ErrorCapture error={error} reset={reset} />;
|
||||
};
|
||||
return ErrorBoundary;
|
||||
};
|
||||
|
||||
// Create error boundaries for each route
|
||||
const ChatErrorBoundary = createErrorBoundary('/chat');
|
||||
const DiscoverErrorBoundary = createErrorBoundary('/discover');
|
||||
const KnowledgeErrorBoundary = createErrorBoundary('/knowledge');
|
||||
const SettingsErrorBoundary = createErrorBoundary('/settings');
|
||||
const ImageErrorBoundary = createErrorBoundary('/image');
|
||||
const ProfileErrorBoundary = createErrorBoundary('/profile');
|
||||
const MeErrorBoundary = createErrorBoundary('/me'); // Mobile only
|
||||
const RootErrorBoundary = createErrorBoundary('/chat'); // Root level falls back to chat
|
||||
|
||||
// Root layout wrapper component
|
||||
const RootLayout = (props: { locale: Locales }) => (
|
||||
<>
|
||||
<NavigatorRegistrar />
|
||||
@@ -34,39 +287,25 @@ 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
|
||||
{
|
||||
children: [
|
||||
{
|
||||
element: <MobileChatPage />,
|
||||
index: true,
|
||||
lazy: () =>
|
||||
import('./(main)/chat/index').then((m) => ({
|
||||
Component: m.MobileChatPage,
|
||||
})),
|
||||
},
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/chat/settings').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <ChatSettings />,
|
||||
path: 'settings',
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/chat/_layout/Mobile').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <ChatLayout />,
|
||||
errorElement: <ChatErrorBoundary />,
|
||||
path: 'chat',
|
||||
},
|
||||
|
||||
@@ -77,116 +316,72 @@ export const createMobileRouter = (locale: Locales) =>
|
||||
{
|
||||
children: [
|
||||
{
|
||||
element: <MobileHomePage />,
|
||||
index: true,
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(list)/(home)/index').then((m) => ({
|
||||
Component: m.MobileHomePage,
|
||||
})),
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(list)/assistant/index').then((m) => ({
|
||||
Component: m.MobileAssistantPage,
|
||||
})),
|
||||
element: <MobileAssistantPage />,
|
||||
path: 'assistant',
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(list)/assistant/_layout/Mobile').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <DiscoverAssistantLayout />,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(list)/model/index').then((m) => ({
|
||||
Component: m.MobileModelPage,
|
||||
})),
|
||||
element: <DiscoverListMobileModelPage />,
|
||||
path: 'model',
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(list)/model/_layout/Mobile').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <DiscoverModelLayout />,
|
||||
},
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(list)/provider/index').then((m) => ({
|
||||
Component: m.MobileProviderPage,
|
||||
})),
|
||||
element: <DiscoverListMobileProviderPage />,
|
||||
path: 'provider',
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(list)/mcp/index').then((m) => ({
|
||||
Component: m.MobileMcpPage,
|
||||
})),
|
||||
element: <DiscoverListMobileMcpPage />,
|
||||
path: 'mcp',
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(list)/mcp/_layout/Mobile').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <DiscoverMcpLayout />,
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(list)/_layout/Mobile/index').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <DiscoverListLayout />,
|
||||
},
|
||||
// Detail routes (with DetailLayout)
|
||||
{
|
||||
children: [
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(detail)/assistant/index').then((m) => ({
|
||||
Component: m.MobileDiscoverAssistantDetailPage,
|
||||
})),
|
||||
element: <MobileDiscoverAssistantDetailPage />,
|
||||
loader: slugLoader,
|
||||
path: 'assistant/:slug',
|
||||
},
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(detail)/model/index').then((m) => ({
|
||||
Component: m.MobileModelPage,
|
||||
})),
|
||||
element: <DiscoverDetailMobileModelPage />,
|
||||
loader: slugLoader,
|
||||
path: 'model/:slug',
|
||||
},
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(detail)/provider/index').then((m) => ({
|
||||
Component: m.MobileProviderPage,
|
||||
})),
|
||||
element: <DiscoverDetailMobileProviderPage />,
|
||||
loader: slugLoader,
|
||||
path: 'provider/:slug',
|
||||
},
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(detail)/mcp/index').then((m) => ({
|
||||
Component: m.MobileMcpPage,
|
||||
})),
|
||||
element: <DiscoverDetailMobileMcpPage />,
|
||||
loader: slugLoader,
|
||||
path: 'mcp/:slug',
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/discover/(detail)/_layout/Mobile/index').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <DiscoverDetailLayout />,
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/discover/_layout/Mobile/index').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <DiscoverLayout />,
|
||||
errorElement: <DiscoverErrorBoundary />,
|
||||
path: 'discover',
|
||||
},
|
||||
|
||||
@@ -194,32 +389,21 @@ export const createMobileRouter = (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/_layout/Mobile').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <KnowledgeLayout />,
|
||||
errorElement: <KnowledgeErrorBoundary />,
|
||||
path: 'knowledge',
|
||||
},
|
||||
|
||||
@@ -227,17 +411,12 @@ export const createMobileRouter = (locale: Locales) =>
|
||||
{
|
||||
children: [
|
||||
{
|
||||
element: <SettingsLayout />,
|
||||
index: true,
|
||||
lazy: () =>
|
||||
import('./(main)/settings/_layout/Mobile').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/settings/_layout/MobileWrapper').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <SettingsLayoutWrapper />,
|
||||
errorElement: <SettingsErrorBoundary />,
|
||||
path: 'settings',
|
||||
},
|
||||
|
||||
@@ -245,26 +424,18 @@ export const createMobileRouter = (locale: Locales) =>
|
||||
{
|
||||
children: [
|
||||
{
|
||||
element: <ImageComingSoon />,
|
||||
index: true,
|
||||
lazy: () =>
|
||||
import('./(main)/image/ComingSoon').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/image/_layout/Mobile').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <ImageLayoutMobile />,
|
||||
errorElement: <ImageErrorBoundary />,
|
||||
path: 'image',
|
||||
},
|
||||
|
||||
// Labs routes
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/labs').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <LabsPage />,
|
||||
path: 'labs',
|
||||
},
|
||||
|
||||
@@ -272,38 +443,24 @@ export const createMobileRouter = (locale: Locales) =>
|
||||
{
|
||||
children: [
|
||||
{
|
||||
element: <ProfileHomePage />,
|
||||
index: true,
|
||||
lazy: () =>
|
||||
import('./(main)/profile/(home)').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').then((m) => ({
|
||||
Component: m.MobileProfileSecurityPage,
|
||||
})),
|
||||
element: <MobileProfileSecurityPage />,
|
||||
path: 'security',
|
||||
},
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/profile/stats').then((m) => ({
|
||||
Component: m.MobileProfileStatsPage,
|
||||
})),
|
||||
element: <MobileProfileStatsPage />,
|
||||
path: 'stats',
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/profile/_layout/Mobile').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <ProfileLayoutMobile />,
|
||||
errorElement: <ProfileErrorBoundary />,
|
||||
path: 'profile',
|
||||
},
|
||||
|
||||
@@ -313,49 +470,32 @@ export const createMobileRouter = (locale: Locales) =>
|
||||
{
|
||||
children: [
|
||||
{
|
||||
element: <MeHomePage />,
|
||||
index: true,
|
||||
lazy: () =>
|
||||
import('./(main)/(mobile)/me/(home)').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/(mobile)/me/(home)/layout').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <MeHomeLayout />,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/(mobile)/me/profile').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <MeProfilePage />,
|
||||
path: 'profile',
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/(mobile)/me/profile/layout').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <MeProfileLayout />,
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
lazy: () =>
|
||||
import('./(main)/(mobile)/me/settings').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <MeSettingsPage />,
|
||||
path: 'settings',
|
||||
},
|
||||
],
|
||||
lazy: () =>
|
||||
import('./(main)/(mobile)/me/settings/layout').then((m) => ({
|
||||
Component: m.default,
|
||||
})),
|
||||
element: <MeSettingsLayout />,
|
||||
},
|
||||
],
|
||||
errorElement: <MeErrorBoundary />,
|
||||
path: 'me',
|
||||
},
|
||||
|
||||
@@ -372,7 +512,7 @@ export const createMobileRouter = (locale: Locales) =>
|
||||
},
|
||||
],
|
||||
element: <RootLayout locale={locale} />,
|
||||
loader: hydrationGateLoader,
|
||||
errorElement: <RootErrorBoundary />,
|
||||
path: '/',
|
||||
},
|
||||
]);
|
||||
|
||||
@@ -12,6 +12,9 @@ export default async (props: DynamicLayoutProps) => {
|
||||
// Conditionally load and render based on device type
|
||||
// Using native dynamic import ensures complete code splitting
|
||||
// Mobile and Desktop bundles will be completely separate
|
||||
|
||||
console.log('isMobile', isMobile);
|
||||
console.log('locale', locale);
|
||||
if (isMobile) {
|
||||
return <MobileRouter locale={locale} />;
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -122,12 +122,6 @@ export interface GlobalState {
|
||||
* 启动时为 Idle,完成为 Ready,报错为 Error
|
||||
*/
|
||||
initClientDBStage: DatabaseLoadingState;
|
||||
/**
|
||||
* 应用水合状态标志
|
||||
* 用于指示客户端状态是否已从 StoreInitialization 完成加载
|
||||
* 默认为 false,StoreInitialization 完成后设置为 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,
|
||||
|
||||
Reference in New Issue
Block a user