Skip to content

Commit b107b5e

Browse files
committed
Performance Optimization
1 parent c46308c commit b107b5e

File tree

5 files changed

+129
-46
lines changed

5 files changed

+129
-46
lines changed

src/App.tsx

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,55 @@
1+
import { lazy, Suspense } from "react";
12
import { Routes, Route } from "react-router-dom";
23
import LandingPage from "./pages/LandingPage";
3-
import Layout from "./components/Layout/Layout";
4-
import DashboardLayout from "./layouts/DashboardLayout";
5-
import Overview from "./pages/Overview";
6-
import Projects from "./pages/Projects";
7-
import Settings from "./pages/Settings";
8-
import Community from "./pages/Community";
9-
import Profile from "./pages/Profile";
10-
import PublicProfile from "./pages/PublicProfile";
114
import ProtectedRoute from "./components/ProtectedRoute/ProtectedRoute";
5+
import LoadingSpinner from "./components/LoadingSpinner/LoadingSpinner";
6+
7+
// Lazy-loaded components for code splitting
8+
const Layout = lazy(() => import("./components/Layout/Layout"));
9+
const DashboardLayout = lazy(() => import("./layouts/DashboardLayout"));
10+
const Overview = lazy(() => import("./pages/Overview"));
11+
const Projects = lazy(() => import("./pages/Projects"));
12+
const Settings = lazy(() => import("./pages/Settings"));
13+
const Community = lazy(() => import("./pages/Community"));
14+
const Profile = lazy(() => import("./pages/Profile"));
15+
const PublicProfile = lazy(() => import("./pages/PublicProfile"));
1216

1317
function App() {
1418
return (
15-
<Routes>
16-
{/* Public Routes */}
17-
<Route path="/" element={<LandingPage />} />
18-
<Route path="/u/:userId" element={<PublicProfile />} />
19+
<Suspense fallback={<LoadingSpinner />}>
20+
<Routes>
21+
{/* Public Routes */}
22+
<Route path="/" element={<LandingPage />} />
23+
<Route path="/u/:userId" element={<PublicProfile />} />
1924

20-
{/* Dashboard with nested routes */}
21-
<Route
22-
path="/dashboard"
23-
element={
24-
<ProtectedRoute>
25-
<DashboardLayout />
26-
</ProtectedRoute>
27-
}
28-
>
29-
<Route index element={<Overview />} />
30-
<Route path="projects" element={<Projects />} />
31-
<Route path="community" element={<Community />} />
32-
<Route path="settings" element={<Settings />} />
33-
<Route path="profile" element={<Profile />} />
34-
<Route path="profile/:userId" element={<Profile />} />
35-
</Route>
25+
{/* Dashboard with nested routes */}
26+
<Route
27+
path="/dashboard"
28+
element={
29+
<ProtectedRoute>
30+
<DashboardLayout />
31+
</ProtectedRoute>
32+
}
33+
>
34+
<Route index element={<Overview />} />
35+
<Route path="projects" element={<Projects />} />
36+
<Route path="community" element={<Community />} />
37+
<Route path="settings" element={<Settings />} />
38+
<Route path="profile" element={<Profile />} />
39+
<Route path="profile/:userId" element={<Profile />} />
40+
</Route>
3641

37-
{/* Editor Route */}
38-
<Route
39-
path="/editor/:id?"
40-
element={
41-
<ProtectedRoute>
42-
<Layout />
43-
</ProtectedRoute>
44-
}
45-
/>
46-
</Routes>
42+
{/* Editor Route */}
43+
<Route
44+
path="/editor/:id?"
45+
element={
46+
<ProtectedRoute>
47+
<Layout />
48+
</ProtectedRoute>
49+
}
50+
/>
51+
</Routes>
52+
</Suspense>
4753
);
4854
}
4955

src/components/CodeEditor/CodeEditor.tsx

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
import { useRef, useEffect } from "react";
2-
import Editor, { type OnMount } from "@monaco-editor/react";
2+
import Editor, { type OnMount, loader } from "@monaco-editor/react";
33
import { emmetHTML, emmetCSS } from "emmet-monaco-es";
44
import type * as Monaco from "monaco-editor";
55
import type { EditorTheme } from "../SettingsModal/SettingsModal";
66

7+
// Configure Monaco Editor to load from CDN instead of bundling
8+
// This reduces bundle size from ~1.2MB to ~50KB
9+
loader.config({
10+
paths: {
11+
vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.45.0/min/vs'
12+
}
13+
});
14+
15+
716
// Custom theme definitions for Monaco
817
const CUSTOM_THEMES: Record<string, Monaco.editor.IStandaloneThemeData> = {
918
dracula: {

src/components/Layout/Layout.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
import { useRef, useState, useCallback, useEffect } from "react";
1+
import { useRef, useState, useCallback, useEffect, lazy, Suspense } from "react";
22
import { useParams, useLocation } from "react-router-dom";
33
import Header from "../Header/Header";
4-
import MainContent from "../MainContent/MainContent";
54
import Footer from "../Footer/Footer";
65
import Dashboard from "../Dashboard/Dashboard";
76
import PublishModal from "../PublishModal/PublishModal";
7+
import LoadingSpinner from "../LoadingSpinner/LoadingSpinner";
88
import type { MainContentRef } from "../MainContent/MainContent";
99
import type { Project } from "../../services/projectService";
1010
import { renameProject, getProjectById, saveProject } from "../../services/projectService";
1111
import { useAuth } from "../../context/AuthContext";
1212

13+
// Lazy load MainContent to reduce initial bundle size
14+
// This separates Monaco Editor (~1.2MB) into a separate chunk
15+
const MainContent = lazy(() => import("../MainContent/MainContent"));
16+
1317

1418

1519
interface LayoutProps {
@@ -297,13 +301,16 @@ h1 {
297301

298302
{/* Main Editor Content */}
299303
<div className={`flex-1 overflow-hidden relative flex flex-col ${showDashboard && user ? 'hidden' : 'flex'}`}>
300-
<MainContent
301-
ref={mainContentRef}
302-
isZenMode={isZenMode}
303-
onCodeChange={currentProjectId && user ? handleCodeChange : undefined}
304-
/>
304+
<Suspense fallback={<LoadingSpinner />}>
305+
<MainContent
306+
ref={mainContentRef}
307+
isZenMode={isZenMode}
308+
onCodeChange={currentProjectId && user ? handleCodeChange : undefined}
309+
/>
310+
</Suspense>
305311
</div>
306312

313+
307314
{/* Dashboard View - Overlay */}
308315
{showDashboard && user && (
309316
<div className="absolute inset-0 top-[48px] z-50 bg-slate-900 overflow-y-auto settings-scrollbar animate-in fade-in duration-200">
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { memo } from 'react';
2+
3+
/**
4+
* LoadingSpinner Component
5+
*
6+
* Displays a centered, animated loading spinner while lazy-loaded route chunks download.
7+
* Used as the fallback component in React Suspense for code-split routes.
8+
*/
9+
function LoadingSpinner() {
10+
return (
11+
<div className="min-h-screen flex items-center justify-center bg-slate-900">
12+
<div className="flex flex-col items-center gap-4">
13+
{/* Spinner Animation */}
14+
<div className="relative w-16 h-16">
15+
{/* Outer ring */}
16+
<div className="absolute inset-0 border-4 border-slate-700 rounded-full"></div>
17+
{/* Spinning gradient ring */}
18+
<div className="absolute inset-0 border-4 border-transparent border-t-purple-500 border-r-purple-400 rounded-full animate-spin"></div>
19+
</div>
20+
21+
{/* Loading text with pulse animation */}
22+
<p className="text-slate-400 text-sm font-medium animate-pulse">
23+
Loading...
24+
</p>
25+
</div>
26+
</div>
27+
);
28+
}
29+
30+
export default memo(LoadingSpinner);

vite.config.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,35 @@ import tailwindcss from '@tailwindcss/vite'
55
// https://vite.dev/config/
66
export default defineConfig({
77
plugins: [react(), tailwindcss()],
8+
build: {
9+
rollupOptions: {
10+
output: {
11+
manualChunks: (id) => {
12+
// Monaco Editor chunk - Large dependency (~3-4MB)
13+
if (id.includes('node_modules/monaco-editor') || id.includes('node_modules/@monaco-editor')) {
14+
return 'monaco-editor';
15+
}
16+
17+
// Firebase chunk - Database and auth SDK
18+
if (id.includes('node_modules/firebase') || id.includes('node_modules/@firebase')) {
19+
return 'firebase';
20+
}
21+
22+
// Vendor chunk - Core React libraries and Framer Motion
23+
if (
24+
id.includes('node_modules/react') ||
25+
id.includes('node_modules/react-dom') ||
26+
id.includes('node_modules/framer-motion')
27+
) {
28+
return 'vendor';
29+
}
30+
},
31+
},
32+
},
33+
},
34+
// Exclude Monaco from Vite's dependency optimization
35+
// This allows it to be loaded from CDN instead
36+
optimizeDeps: {
37+
exclude: ['monaco-editor']
38+
}
839
})

0 commit comments

Comments
 (0)