diff --git a/frontend/index.html b/frontend/index.html index 89b5c40..1843ea6 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,9 +4,19 @@ + - + + + + + + + + + + Rainbow, Cupcakes & Unicorns - Free Games for Kids @@ -14,6 +24,3 @@ - - - diff --git a/frontend/public/tic-tac-toe.png b/frontend/public/tic-tac-toe.png new file mode 100644 index 0000000..230e556 Binary files /dev/null and b/frontend/public/tic-tac-toe.png differ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8b668cc..7522a36 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,17 +1,30 @@ import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { Suspense, lazy } from 'react'; import { AuthProvider } from './hooks/useAuth'; import { ErrorBoundary } from './components/ErrorBoundary'; import { Navbar } from './components/Navbar/Navbar'; import { Footer } from './components/Footer/Footer'; import { ProtectedRoute } from './components/ProtectedRoute'; import { LandingPage } from './pages/LandingPage'; -import { AdminPage } from './pages/AdminPage'; -import { VideosAdminPage } from './pages/VideosAdminPage'; -import { SpeechSoundsAdminPage } from './pages/SpeechSoundsAdminPage'; -import { LoginPage } from './pages/LoginPage'; import { APPS } from './config/apps'; import './globals.css'; +// Lazy load admin and login pages +const AdminPage = lazy(() => import('./pages/AdminPage').then(module => ({ default: module.AdminPage }))); +const VideosAdminPage = lazy(() => import('./pages/VideosAdminPage').then(module => ({ default: module.VideosAdminPage }))); +const SpeechSoundsAdminPage = lazy(() => import('./pages/SpeechSoundsAdminPage').then(module => ({ default: module.SpeechSoundsAdminPage }))); +const LoginPage = lazy(() => import('./pages/LoginPage').then(module => ({ default: module.LoginPage }))); + +// Loading fallback component +const PageLoader = () => ( +
+
+
+

Loading...

+
+
+); + function App() { return ( @@ -20,43 +33,49 @@ function App() {
- - } /> - {/* Dynamically generate routes for enabled apps */} - {APPS.filter(app => !app.disabled).map(app => ( - } + }> + + } /> + {/* Dynamically generate routes for enabled apps */} + {APPS.filter(app => !app.disabled).map(app => ( + }> + + + } + /> + ))} + {/* Keep non-app routes separate */} + } /> + + + + } + /> + + + + } + /> + + + + } /> - ))} - {/* Keep non-app routes separate */} - } /> - - - - } - /> - - - - } - /> - - - - } - /> - + +
diff --git a/frontend/src/components/Navbar/Navbar.tsx b/frontend/src/components/Navbar/Navbar.tsx index 58a2dac..68875e3 100644 --- a/frontend/src/components/Navbar/Navbar.tsx +++ b/frontend/src/components/Navbar/Navbar.tsx @@ -72,17 +72,25 @@ export function Navbar() {
- Rainbow + Rainbow

Rainbows, Cupcakes & Unicorns

- Cupcake + Cupcake
diff --git a/frontend/src/config/apps.ts b/frontend/src/config/apps.ts index b42e3f2..8930fd7 100644 --- a/frontend/src/config/apps.ts +++ b/frontend/src/config/apps.ts @@ -1,7 +1,9 @@ -import React from 'react'; -import { VideoApp } from '../pages/VideoApp'; -import { SpeechSoundsApp } from '../pages/SpeechSoundsApp'; -import { TicTacToeApp } from '../pages/TicTacToeApp'; +import React, { lazy } from 'react'; + +// Lazy load game pages for code splitting +const VideoApp = lazy(() => import('../pages/VideoApp').then(module => ({ default: module.VideoApp }))); +const SpeechSoundsApp = lazy(() => import('../pages/SpeechSoundsApp').then(module => ({ default: module.SpeechSoundsApp }))); +const TicTacToeApp = lazy(() => import('../pages/TicTacToeApp').then(module => ({ default: module.TicTacToeApp }))); export type App = { id: string; diff --git a/frontend/src/pages/LandingPage.tsx b/frontend/src/pages/LandingPage.tsx index ffb18db..15ac610 100644 --- a/frontend/src/pages/LandingPage.tsx +++ b/frontend/src/pages/LandingPage.tsx @@ -28,6 +28,7 @@ export function LandingPage() {
+ {/* First card is likely LCP element - prioritize it */}
{APPS.map(app => { const color = categoryColors[app.id] || 'pink'; @@ -52,12 +53,30 @@ export function LandingPage() { src="/video-marketing.png" alt="Video App" className="w-20 h-20 object-contain" + width="80" + height="80" + loading="eager" + fetchPriority={app.id === 'videos' ? 'high' : 'auto'} /> ) : app.id === 'speechsounds' ? ( Speech Sounds + ) : app.id === 'tictactoe' ? ( + Tic Tac Toe ) : ( {emoji} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1f97208..f45dfb6 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -5,6 +5,22 @@ export default defineConfig({ plugins: [react()], server: { port: 5173 + }, + build: { + rollupOptions: { + output: { + manualChunks: { + 'react-vendor': ['react', 'react-dom', 'react-router-dom'], + 'ui-vendor': ['axios'] + } + } + }, + chunkSizeWarningLimit: 1000, + cssCodeSplit: true, + minify: 'esbuild' + }, + optimizeDeps: { + include: ['react', 'react-dom', 'react-router-dom'] } })