perf(web): lazy-load app routes and heavy surfaces

This commit is contained in:
root 2026-05-06 17:12:22 +00:00
parent e853ffc0c5
commit 7de6b236c0
3 changed files with 59 additions and 27 deletions

View File

@ -1,10 +1,9 @@
import { useState, useEffect, useCallback, useRef } from 'react'; import { Suspense, lazy, useState, useEffect, useCallback, useRef } from 'react';
import { BrowserRouter } from 'react-router-dom'; import { BrowserRouter } from 'react-router-dom';
import { useWebSocket } from './hooks/useWebSocket'; import { useWebSocket } from './hooks/useWebSocket';
import { useAuth } from './components/AuthContext'; import { useAuth } from './components/AuthContext';
import { Login } from './components/Login'; import { Login } from './components/Login';
import { ResetPassword } from './components/ResetPassword'; import { ResetPassword } from './components/ResetPassword';
import { ChatControl } from './components/ChatControl';
import { AppContext } from './context/AppContext'; import { AppContext } from './context/AppContext';
import { AppShell } from './components/layout/AppShell'; import { AppShell } from './components/layout/AppShell';
import { useBacktestFeatureGate } from './backtest/useBacktestFeatureGate'; import { useBacktestFeatureGate } from './backtest/useBacktestFeatureGate';
@ -12,6 +11,8 @@ import { useTabFeatureFlags } from './hooks/useTabFeatureFlags';
import { tradingRuntime, tradingTelemetry } from './lib/runtime'; import { tradingRuntime, tradingTelemetry } from './lib/runtime';
import { createTradeProfile, fetchTradeProfiles, updateTradeProfile } from './lib/profileApi'; import { createTradeProfile, fetchTradeProfiles, updateTradeProfile } from './lib/profileApi';
const ChatControl = lazy(() => import('./components/ChatControl').then((mod) => ({ default: mod.ChatControl })));
// ─── Helpers (preserved from original App.tsx) ─────────────────────────────── // ─── Helpers (preserved from original App.tsx) ───────────────────────────────
export const resolveProfileNameForAction = ( export const resolveProfileNameForAction = (
@ -262,7 +263,9 @@ function App() {
)} )}
{/* Floating AI strategy assistant */} {/* Floating AI strategy assistant */}
<Suspense fallback={null}>
<ChatControl profiles={chatProfiles} onApplyProfile={handleChatApply} /> <ChatControl profiles={chatProfiles} onApplyProfile={handleChatApply} />
</Suspense>
</AppContext.Provider> </AppContext.Provider>
</BrowserRouter> </BrowserRouter>
); );

View File

@ -1,17 +1,41 @@
import { Suspense, lazy } from 'react';
import { Link, Routes, Route, useLocation } from 'react-router-dom'; import { Link, Routes, Route, useLocation } from 'react-router-dom';
import { Sidebar } from './Sidebar'; import { Sidebar } from './Sidebar';
import { Header } from './Header'; import { Header } from './Header';
import { RightPanel } from './RightPanel'; import { RightPanel } from './RightPanel';
import { Button } from '../ui/button'; import { Button } from '../ui/button';
import { HomeView } from '../../views/HomeView';
import { PortfolioView } from '../../views/PortfolioView'; const HomeView = lazy(() => import('../../views/HomeView').then((mod) => ({ default: mod.HomeView })));
import { ResearchView } from '../../views/ResearchView'; const PortfolioView = lazy(() => import('../../views/PortfolioView').then((mod) => ({ default: mod.PortfolioView })));
import { SimpleView } from '../../views/SimpleView'; const ResearchView = lazy(() => import('../../views/ResearchView').then((mod) => ({ default: mod.ResearchView })));
import { MarketsView } from '../../views/MarketsView'; const SimpleView = lazy(() => import('../../views/SimpleView').then((mod) => ({ default: mod.SimpleView })));
import { ScreenerView } from '../../views/ScreenerView'; const MarketsView = lazy(() => import('../../views/MarketsView').then((mod) => ({ default: mod.MarketsView })));
import { WatchlistView } from '../../views/WatchlistView'; const ScreenerView = lazy(() => import('../../views/ScreenerView').then((mod) => ({ default: mod.ScreenerView })));
import { AlertsView } from '../../views/AlertsView'; const WatchlistView = lazy(() => import('../../views/WatchlistView').then((mod) => ({ default: mod.WatchlistView })));
import { SettingsView } from '../../views/SettingsView'; const AlertsView = lazy(() => import('../../views/AlertsView').then((mod) => ({ default: mod.AlertsView })));
const SettingsView = lazy(() => import('../../views/SettingsView').then((mod) => ({ default: mod.SettingsView })));
function RouteFallback() {
return (
<section
aria-live="polite"
style={{
minHeight: 320,
borderRadius: 24,
border: '1px solid var(--border)',
background: 'var(--card)',
color: 'var(--foreground)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 14,
fontWeight: 700,
}}
>
Loading workspace
</section>
);
}
function NotFoundView() { function NotFoundView() {
const location = useLocation(); const location = useLocation();
@ -64,6 +88,7 @@ export function AppShell() {
<div className="dashboard-content-row"> <div className="dashboard-content-row">
{/* Scrollable main content */} {/* Scrollable main content */}
<main className="dashboard-content"> <main className="dashboard-content">
<Suspense fallback={<RouteFallback />}>
<Routes> <Routes>
<Route path="/" element={<HomeView />} /> <Route path="/" element={<HomeView />} />
<Route path="/portfolio" element={<PortfolioView />} /> <Route path="/portfolio" element={<PortfolioView />} />
@ -76,6 +101,7 @@ export function AppShell() {
<Route path="/settings" element={<SettingsView />} /> <Route path="/settings" element={<SettingsView />} />
<Route path="*" element={<NotFoundView />} /> <Route path="*" element={<NotFoundView />} />
</Routes> </Routes>
</Suspense>
</main> </main>
{/* Fixed right panel */} {/* Fixed right panel */}

View File

@ -1,10 +1,11 @@
import { useState } from 'react'; import { Suspense, lazy, useState } from 'react';
import { useAppContext } from '../context/AppContext'; import { useAppContext } from '../context/AppContext';
import { MarketplaceTab } from '../tabs/MarketplaceTab';
import { TopVolatile, AISetups } from '../components/MarketOpportunities'; import { TopVolatile, AISetups } from '../components/MarketOpportunities';
import type { StrategyPreset } from '../lib/PresetRegistry'; import type { StrategyPreset } from '../lib/PresetRegistry';
import { PageHeader } from '../components/ui/page-header'; import { PageHeader } from '../components/ui/page-header';
const MarketplaceTab = lazy(() => import('../tabs/MarketplaceTab').then((mod) => ({ default: mod.MarketplaceTab })));
export function MarketsView() { export function MarketsView() {
const { botState, showMarketplaceTab } = useAppContext(); const { botState, showMarketplaceTab } = useAppContext();
const [, setClonedPreset] = useState<StrategyPreset | null>(null); const [, setClonedPreset] = useState<StrategyPreset | null>(null);
@ -22,7 +23,9 @@ export function MarketsView() {
<AISetups botState={botState} /> <AISetups botState={botState} />
</div> </div>
{showMarketplaceTab && ( {showMarketplaceTab && (
<Suspense fallback={null}>
<MarketplaceTab onClone={handleClone} botState={botState} /> <MarketplaceTab onClone={handleClone} botState={botState} />
</Suspense>
)} )}
</div> </div>
); );