From 1c268555f69243bb4c2383d8395dd00fdbcd7ae3 Mon Sep 17 00:00:00 2001 From: Lars Date: Wed, 13 May 2026 22:10:02 +0200 Subject: [PATCH] feat(App): implement code-splitting for improved performance and user experience - Refactored the App component to utilize React's lazy loading for page components, enhancing load times and performance. - Introduced a fallback UI with a spinner during component loading, improving user feedback during navigation. - Updated the AuthContext to use useCallback and useMemo for optimized performance in login and logout functions, reducing unnecessary re-renders. Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/App.jsx | 83 ++++++++++++++++++---------- frontend/src/context/AuthContext.jsx | 39 ++++++++----- frontend/vite.config.js | 31 ++++++++++- 3 files changed, 107 insertions(+), 46 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5f05dae..65c338e 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { Suspense, lazy } from 'react' import { RouterProvider, createBrowserRouter, @@ -12,45 +12,66 @@ import { ToastProvider } from './context/ToastContext' import { OrgInboxProvider, useOrgInbox } from './context/OrgInboxContext' import DesktopSidebar from './components/DesktopSidebar' import { getMainNavItems } from './config/appNav' -import LoginPage from './pages/LoginPage' -import VerifyPage from './pages/VerifyPage' -import Dashboard from './pages/Dashboard' -import AccountSettingsPage from './pages/AccountSettingsPage' -import SettingsSystemInfoPage from './pages/SettingsSystemInfoPage' -import ExercisesListPage from './pages/ExercisesListPage' -import ExerciseDetailPage from './pages/ExerciseDetailPage' -import ExerciseFormPage from './pages/ExerciseFormPage' -import ClubsPage from './pages/ClubsPage' -import InboxPage from './pages/InboxPage' -import SkillsPage from './pages/SkillsPage' -import TrainingPlanningPage from './pages/TrainingPlanningPage' -import TrainingFrameworkProgramsListPage from './pages/TrainingFrameworkProgramsListPage' -import TrainingFrameworkProgramEditPage from './pages/TrainingFrameworkProgramEditPage' -import TrainingModulesListPage from './pages/TrainingModulesListPage' -import TrainingModuleEditPage from './pages/TrainingModuleEditPage' -import TrainingUnitRunPage from './pages/TrainingUnitRunPage' -import TrainingCoachPage from './pages/TrainingCoachPage' -import AdminCatalogsPage from './pages/AdminCatalogsPage' -import AdminHierarchyPage from './pages/AdminHierarchyPage' -import AdminMaturityModelsPage from './pages/AdminMaturityModelsPage' -import TrainerContextsPage from './pages/TrainerContextsPage' -import MediaWikiImportPage from './pages/MediaWikiImportPage' -import AdminUsersPage from './pages/AdminUsersPage' import AdminHomeRedirect from './components/AdminHomeRedirect' import PlatformAdminRoute from './components/PlatformAdminRoute' -import MediaLibraryPage from './pages/MediaLibraryPage' -import LegalPage from './pages/LegalPage' -import AdminLegalDocumentsPage from './pages/AdminLegalDocumentsPage' -import SettingsLegalPage from './pages/SettingsLegalPage' import ActiveClubSwitcher from './components/ActiveClubSwitcher' import InactiveMembershipBanner from './components/InactiveMembershipBanner' import './app.css' +const LoginPage = lazy(() => import('./pages/LoginPage')) +const VerifyPage = lazy(() => import('./pages/VerifyPage')) +const Dashboard = lazy(() => import('./pages/Dashboard')) +const AccountSettingsPage = lazy(() => import('./pages/AccountSettingsPage')) +const SettingsSystemInfoPage = lazy(() => import('./pages/SettingsSystemInfoPage')) +const ExercisesListPage = lazy(() => import('./pages/ExercisesListPage')) +const ExerciseDetailPage = lazy(() => import('./pages/ExerciseDetailPage')) +const ExerciseFormPage = lazy(() => import('./pages/ExerciseFormPage')) +const ClubsPage = lazy(() => import('./pages/ClubsPage')) +const InboxPage = lazy(() => import('./pages/InboxPage')) +const SkillsPage = lazy(() => import('./pages/SkillsPage')) +const TrainingPlanningPage = lazy(() => import('./pages/TrainingPlanningPage')) +const TrainingFrameworkProgramsListPage = lazy(() => + import('./pages/TrainingFrameworkProgramsListPage'), +) +const TrainingFrameworkProgramEditPage = lazy(() => + import('./pages/TrainingFrameworkProgramEditPage'), +) +const TrainingModulesListPage = lazy(() => import('./pages/TrainingModulesListPage')) +const TrainingModuleEditPage = lazy(() => import('./pages/TrainingModuleEditPage')) +const TrainingUnitRunPage = lazy(() => import('./pages/TrainingUnitRunPage')) +const TrainingCoachPage = lazy(() => import('./pages/TrainingCoachPage')) +const AdminCatalogsPage = lazy(() => import('./pages/AdminCatalogsPage')) +const AdminHierarchyPage = lazy(() => import('./pages/AdminHierarchyPage')) +const AdminMaturityModelsPage = lazy(() => import('./pages/AdminMaturityModelsPage')) +const TrainerContextsPage = lazy(() => import('./pages/TrainerContextsPage')) +const MediaWikiImportPage = lazy(() => import('./pages/MediaWikiImportPage')) +const AdminUsersPage = lazy(() => import('./pages/AdminUsersPage')) +const MediaLibraryPage = lazy(() => import('./pages/MediaLibraryPage')) +const LegalPage = lazy(() => import('./pages/LegalPage')) +const AdminLegalDocumentsPage = lazy(() => import('./pages/AdminLegalDocumentsPage')) +const SettingsLegalPage = lazy(() => import('./pages/SettingsLegalPage')) + /** Shield „Admin“: nur Super-Admin (global). Vereinsorga: Vereine → Mitglieder. */ function computeShowAdminNav(currentUser) { return currentUser?.role === 'superadmin' } +function AppRouteFallback() { + return ( +
+
+
+ ) +} + // Bottom Navigation (Mobile) function Nav({ showAdminNav }) { const { canAccessOrgInbox, inboxCount } = useOrgInbox() @@ -270,7 +291,9 @@ function App() { return ( - + }> + + ) diff --git a/frontend/src/context/AuthContext.jsx b/frontend/src/context/AuthContext.jsx index f02526d..50a877e 100644 --- a/frontend/src/context/AuthContext.jsx +++ b/frontend/src/context/AuthContext.jsx @@ -1,4 +1,12 @@ -import { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react' +import { + createContext, + useContext, + useState, + useEffect, + useCallback, + useMemo, + useRef, +} from 'react' import api, { ACTIVE_CLUB_STORAGE_KEY } from '../utils/api' import { activeClubMemberships } from '../utils/activeClub' @@ -94,7 +102,7 @@ export function AuthProvider({ children }) { }, []) /** Fallback, falls ohne checkAuth gesetzt wird (Legacy / Token-Injektion) */ - const login = (payload) => { + const login = useCallback((payload) => { if (payload?.profile != null) { syncStoredActiveClub(payload.profile) setUser(payload.profile) @@ -112,9 +120,9 @@ export function AuthProvider({ children }) { return } setUser(payload) - } + }, []) - const logout = () => { + const logout = useCallback(() => { setUser(null) localStorage.removeItem('authToken') localStorage.removeItem(ACTIVE_CLUB_STORAGE_KEY) @@ -123,17 +131,20 @@ export function AuthProvider({ children }) { sessionStorage.removeItem(key) } } - } + }, []) - const value = { - user, - isAuthenticated: !!user, - loading, - login, - logout, - checkAuth, - setActiveClub, - } + const value = useMemo( + () => ({ + user, + isAuthenticated: !!user, + loading, + login, + logout, + checkAuth, + setActiveClub, + }), + [user, loading, login, logout, checkAuth, setActiveClub], + ) return ( diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 4b68d47..06a2b1e 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -9,6 +9,33 @@ export default defineConfig({ }, build: { outDir: 'dist', - sourcemap: false - } + sourcemap: false, + rollupOptions: { + output: { + manualChunks(id) { + if (!id.includes('node_modules')) return + if (id.includes('jspdf')) return 'vendor-pdf' + if (id.includes('lucide-react')) return 'vendor-icons' + if ( + id.includes('react-markdown') || + id.includes('/marked/') || + id.includes('remark-') || + id.includes('mdast') || + id.includes('micromark') || + id.includes('unist') + ) { + return 'vendor-markdown' + } + if (id.includes('react-router')) return 'vendor-router' + if ( + /[/\\]node_modules[/\\]react-dom[/\\]/.test(id) || + /[/\\]node_modules[/\\]react[/\\]/.test(id) || + /[/\\]node_modules[/\\]scheduler[/\\]/.test(id) + ) { + return 'vendor-react' + } + }, + }, + }, + }, })