feat(App): implement code-splitting for improved performance and user experience
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 56s

- 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 <noreply@anthropic.com>
This commit is contained in:
Lars 2026-05-13 22:10:02 +02:00
parent 85163ad440
commit 1c268555f6
3 changed files with 107 additions and 46 deletions

View File

@ -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 (
<div
style={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'var(--bg)',
}}
>
<div className="spinner"></div>
</div>
)
}
// Bottom Navigation (Mobile)
function Nav({ showAdminNav }) {
const { canAccessOrgInbox, inboxCount } = useOrgInbox()
@ -270,7 +291,9 @@ function App() {
return (
<AuthProvider>
<ToastProvider>
<RouterProvider router={appRouter} />
<Suspense fallback={<AppRouteFallback />}>
<RouterProvider router={appRouter} />
</Suspense>
</ToastProvider>
</AuthProvider>
)

View File

@ -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 (
<AuthContext.Provider value={value}>

View File

@ -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'
}
},
},
},
},
})