Skip to content

Commit 264a556

Browse files
Mahalakshmicursoragent
andauthored
Refactor: Optimize performance and add lazy loading (#2)
Implement lazy loading for components and images. Add preconnect hints for fonts. Remove unused reportWebVitals. Co-authored-by: Cursor Agent <cursoragent@cursor.com>
1 parent 4ba8ab9 commit 264a556

14 files changed

Lines changed: 116 additions & 47 deletions

public/index.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
work correctly both with client-side routing and a non-root public URL.
2525
Learn how to configure a non-root public URL by running `npm run build`.
2626
-->
27+
<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin>
28+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
29+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
2730
<title>Mahalakshmi's Portfolio</title>
2831
<!-- Dancing Script -->
2932
<link href="https://fonts.googleapis.com/css2?family=Dancing+Script:wght@700&display=swap" rel="stylesheet">

src/App.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
/* App.css - Custom styles to complement Bootstrap */
2-
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
32

43
:root {
54
/* Light theme variables */

src/App.js

Lines changed: 56 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,41 @@
1-
import React from 'react';
1+
import React, { useEffect, useRef, useState, Suspense } from 'react';
22
import 'bootstrap/dist/css/bootstrap.min.css';
33
import './App.css';
44

55
import { ThemeProvider } from './contexts/ThemeContext';
66
import Navbar from './components/Navbar';
77
import Hero from './components/Hero';
8-
import About from './components/About';
9-
import Projects from './components/Projects';
10-
import Contact from './components/Contact';
11-
import Footer from './components/Footer';
128
import DecorativeElement from './components/DecorativeElement';
13-
import Timeline from './components/Timeline';
9+
const About = React.lazy(() => import('./components/About'));
10+
const Projects = React.lazy(() => import('./components/Projects'));
11+
const Contact = React.lazy(() => import('./components/Contact'));
12+
const Footer = React.lazy(() => import('./components/Footer'));
13+
const Timeline = React.lazy(() => import('./components/Timeline'));
14+
15+
function LazySection({ children, rootMargin = '200px' }) {
16+
const [isVisible, setIsVisible] = useState(false);
17+
const containerRef = useRef(null);
18+
19+
useEffect(() => {
20+
const observer = new IntersectionObserver(
21+
([entry]) => {
22+
if (entry.isIntersecting) {
23+
setIsVisible(true);
24+
observer.disconnect();
25+
}
26+
},
27+
{ root: null, rootMargin, threshold: 0.1 }
28+
);
29+
30+
if (containerRef.current) {
31+
observer.observe(containerRef.current);
32+
}
33+
34+
return () => observer.disconnect();
35+
}, [rootMargin]);
36+
37+
return <div ref={containerRef}>{isVisible ? children : null}</div>;
38+
}
1439

1540
function App() {
1641
return (
@@ -19,14 +44,34 @@ function App() {
1944
<Navbar />
2045
<Hero />
2146
<DecorativeElement type="music-note" position="right" />
22-
<About />
47+
<LazySection>
48+
<Suspense fallback={null}>
49+
<About />
50+
</Suspense>
51+
</LazySection>
2352
<DecorativeElement type="music-note" position="left" />
24-
<Timeline />
53+
<LazySection>
54+
<Suspense fallback={null}>
55+
<Timeline />
56+
</Suspense>
57+
</LazySection>
2558
<DecorativeElement type="diamond" position="right" />
26-
<Projects />
59+
<LazySection>
60+
<Suspense fallback={null}>
61+
<Projects />
62+
</Suspense>
63+
</LazySection>
2764
<DecorativeElement type="corner" position="right" />
28-
<Contact />
29-
<Footer />
65+
<LazySection>
66+
<Suspense fallback={null}>
67+
<Contact />
68+
</Suspense>
69+
</LazySection>
70+
<LazySection>
71+
<Suspense fallback={null}>
72+
<Footer />
73+
</Suspense>
74+
</LazySection>
3075
</div>
3176
</ThemeProvider>
3277
);

src/components/About.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React from 'react';
2-
import { Container, Row, Col, Button } from 'react-bootstrap';
2+
import Container from 'react-bootstrap/Container';
3+
import Row from 'react-bootstrap/Row';
4+
import Col from 'react-bootstrap/Col';
5+
import Button from 'react-bootstrap/Button';
36
import { SiReact, SiNodedotjs, SiJavascript, SiTypescript, SiBootstrap, SiExpress, SiMongodb, SiNextdotjs, SiHtml5, SiSass, SiCypress, SiJest } from 'react-icons/si';
47
import { motion } from 'framer-motion';
58

@@ -30,6 +33,10 @@ const About = () => {
3033
src="assets/images/Profile.png"
3134
alt="Profile-Pic"
3235
className="img-fluid rounded-circle border border-3"
36+
loading="lazy"
37+
decoding="async"
38+
width="300"
39+
height="300"
3340
onError={(e) => {
3441
e.target.onerror = null;
3542
e.target.src = 'https://via.placeholder.com/300x300';

src/components/Contact.jsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import React, { useState } from 'react';
2-
import { Container, Row, Col, Form, Button } from 'react-bootstrap';
2+
import Container from 'react-bootstrap/Container';
3+
import Row from 'react-bootstrap/Row';
4+
import Col from 'react-bootstrap/Col';
5+
import Form from 'react-bootstrap/Form';
6+
import Button from 'react-bootstrap/Button';
37
import { FaGithub, FaLinkedin, FaInstagram, FaArrowRight } from 'react-icons/fa';
4-
import emailjs from '@emailjs/browser';
58
import { motion } from 'framer-motion';
69

710
const Contact = () => {
@@ -10,7 +13,7 @@ const Contact = () => {
1013
const [message, setMessage] = useState('');
1114
const [status, setStatus] = useState(null);
1215

13-
const handleSubmit = (e) => {
16+
const handleSubmit = async (e) => {
1417
e.preventDefault();
1518
setStatus('loading');
1619
console.log({ name, email, message });
@@ -26,7 +29,8 @@ const Contact = () => {
2629
message: message,
2730
}
2831

29-
//To send email using EmailJS
32+
//To send email using EmailJS (dynamically imported to reduce initial bundle size)
33+
const { default: emailjs } = await import('@emailjs/browser');
3034
emailjs.send(serviceId, templateId, templateParams, publicKey).then((response) => {
3135
setStatus('success');
3236
setName('');
@@ -62,6 +66,10 @@ const Contact = () => {
6266
src="assets/images/Profile.png"
6367
alt="Profile"
6468
className="profile-picture"
69+
loading="lazy"
70+
decoding="async"
71+
width="160"
72+
height="160"
6573
onError={(e) => {
6674
e.target.onerror = null;
6775
e.target.src = 'https://via.placeholder.com/160x160';

src/components/DecorativeElement.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,4 @@ const DecorativeElement = ({ type, position }) => {
4343
);
4444
};
4545

46-
export default DecorativeElement;
46+
export default React.memo(DecorativeElement);

src/components/Footer.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { FaArrowUp, FaGithub, FaLinkedin, FaInstagram } from 'react-icons/fa';
33
import { motion } from 'framer-motion';
44
import { useTheme } from '../contexts/ThemeContext';
55

6-
const Footer = () => {
6+
const Footer = React.memo(() => {
77
const { darkMode } = useTheme();
88

99
// Dynamic styles based on theme
@@ -68,6 +68,6 @@ const Footer = () => {
6868
</div>
6969
</motion.footer>
7070
);
71-
};
71+
});
7272

7373
export default Footer;

src/components/Hero.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React from 'react';
2-
import { Container, Row, Col, Button } from 'react-bootstrap';
2+
import Container from 'react-bootstrap/Container';
3+
import Row from 'react-bootstrap/Row';
4+
import Col from 'react-bootstrap/Col';
5+
import Button from 'react-bootstrap/Button';
36
import { FaArrowRight, FaSmileBeam } from 'react-icons/fa';
47
import { Typewriter } from 'react-simple-typewriter';
58
import { motion } from 'framer-motion';
@@ -57,6 +60,10 @@ const Hero = () => {
5760
src="assets/images/dev-type.svg"
5861
alt="Developer coding"
5962
className="img-fluid"
63+
loading="eager"
64+
decoding="async"
65+
width="800"
66+
height="600"
6067
style={{
6168
maxWidth: '190%',
6269
transform: 'translateX(5%) scale(1.5)',

src/components/Navbar.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import React from 'react';
2-
import { Navbar as BootstrapNavbar, Nav, Container, Button } from 'react-bootstrap';
2+
import BootstrapNavbar from 'react-bootstrap/Navbar';
3+
import Nav from 'react-bootstrap/Nav';
4+
import Container from 'react-bootstrap/Container';
5+
import Button from 'react-bootstrap/Button';
36
import { useTheme } from '../contexts/ThemeContext';
47
import { FaLinkedin, FaGithub } from 'react-icons/fa';
58
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';

src/components/ProjectCard.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ const ProjectCard = ({ project }) => {
3939
src={project.image}
4040
alt={project.title}
4141
className="project-image"
42+
loading="lazy"
43+
decoding="async"
44+
width={120}
45+
height={120}
4246
onError={(e) => {
4347
e.target.onerror = null;
4448
e.target.src = 'https://via.placeholder.com/120';
@@ -97,4 +101,4 @@ const ProjectCard = ({ project }) => {
97101
);
98102
};
99103

100-
export default ProjectCard;
104+
export default React.memo(ProjectCard);

0 commit comments

Comments
 (0)