A high-performance React 19 + Vite 6 + Zustand application that fetches and displays images in a virtualized masonry grid layout.
The app supports dynamic searching, infinite scrolling, and image details view, all optimized for performance.
You can view the deployed application here:
π Masonry Grid App Live Demo
The app is hosted on GitHub Pages and reflects the latest production build from the main
branch via GitHub Actions CI/CD.
β
Masonry Grid Layout β Displays images dynamically in a structured format.
β
Infinite Scrolling β Seamless user experience without page reloads.
β
Debounced Search β Searches images dynamically while reducing API calls.
β
Error Handling & Error Boundaries β Ensures smooth user experience in case of failures.
β
React Router 7 β Handles navigation efficiently.
β
SEO Meta Tags β Improves discoverability.
β
Bundle Optimization β Ensures small, fast-loading JavaScript chunks.
β
Unit β Uses Vitest
and React Testing Library
.
β
GitHub Actions Deployment β Automates testing and deployment to GitHub Pages.
Technology | Purpose |
---|---|
React v19 | Core framework |
Vite | Fast build tooling |
Zustand | State management |
Styled-Components | CSS-in-JS styling |
React Router | Client-side navigation |
Window Scroll API | Virtualization & lazy loading |
Pexels API | Image source |
npm install
Create a .env.local
file:
VITE_PEXELS_API_KEY=your-api-key
npm run dev
Then, open http://localhost:5173 in the browser.
npm run build
npm run preview
This runs the app as it will be served in production.
To handle large datasets efficiently, the app uses:
- CSS Grid (
grid-auto-flow: dense;
) for responsive layout. - Row-based virtualization to only render visible images.
- Corrected row height calculations for precise layout using dynamic column width.
- Lazy-loaded images for reduced LCP (Largest Contentful Paint)
- Lazy-loaded routes (
React.lazy()
) ensure minimal initial bundle size. basename
configured (/masonry-grid-app/
) for GitHub Pages compatibility.
- Prevents duplicate API requests using state tracking.
- Persists search queries for a smooth user experience.
- Vite's
manualChunks
ensures separate vendor bundles forreact-router
,zustand
, andstyled-components
. rollupOptions
tree-shaking aggressively removes unused dependencies.- Cache-busting filename hashing for better performance.
useEffect
withsetTimeout
to debounce API calls (500ms delay).- Ensures fewer network requests while keeping the UI responsive.
- Strict TypeScript settings (
strict: true
) prevent runtime errors. - Typed API responses (
Photo
interface) ensure safe data handling. - Full type safety in Zustand store, React Router, and components.
- GitHub Actions handles deployment to GitHub Pages.
- Versioned asset filenames (
[name].[hash].js
) prevent stale cache issues. - Custom
_headers
(attempted) β Fallback to hashed filenames for caching.
Ensuring a high-performance user experience was a key priority in the development of this application. Utilized Google Lighthouse and Chrome DevTools Performance Tab to track and improve metrics such as load time, rendering efficiency, and interactivity.
Ran Lighthouse audits on both mobile and desktop, and achieved the following scores:
Category | Desktop Score | Mobile Score |
---|---|---|
Performance | π’ 100 | π’ 97 |
Accessibility | π’ 100 | π’ 100 |
Best Practices | π’ 100 | π’ 100 |
SEO | π’ 100 | π’ 100 |
π Final Lighthouse Scores:
Desktop:
Used the Performance Profiler in Chrome DevTools to:
- Track JavaScript execution times and reduce long tasks (blocking UI threads).
- Measure render time to optimize virtualized image loading.
- Identify unnecessary re-renders using the React Profiler.
π Key Fixes:
- Virtualized Masonry Grid ensures only visible items are rendered, reducing DOM updates.
- Debounced Search API Calls (500ms delay) minimizes redundant network requests.
- Code-Splitting with
manualChunks
in Vite to reduce initial bundle size.
Using Chromeβs Network Panel, tracked:
- API call times (optimized with Zustandβs state management).
- Lazy-loaded images with placeholders to prevent layout shifts (CLS issues).
- Caching & Asset Optimization using:
- Versioned filenames (
[name].[hash].js
) for efficient browser caching. - GitHub Pages Cache Strategy ensuring HTML is always fresh, while assets are cached for a year.
- Versioned filenames (
Using Google Lighthouse & Chrome DevTools:
- Tracked app performance issues in real time.
- Optimized virtualized rendering & API calls.
- Ensured best practices for accessibility, SEO, and security.
- Achieved near-perfect Lighthouse scores.
Throughout development, made several technology choices based on performance, maintainability, and best practices. Below is a summary of the key decisions:
β Vite 6 was chosen over Webpack because:
- Faster Development (HMR) π β Vite uses native ES modules for instant hot module replacement (HMR), whereas Webpack rebuilds the entire bundle.
- Smaller Bundles π¦ β Vite automatically tree-shakes and removes dead code, whereas Webpack often requires manual optimization via plugins.
- Lightning-Fast Builds β‘ β Vite pre-bundles dependencies using esbuild, which is ~10-100x faster than Webpack's Babel-based bundling.
- Better Code Splitting & Lazy Loading β Vite handles dynamic imports more efficiently with Rollupβs output optimizations.
- Simplified Configuration β Webpack requires complex
webpack.config.js
and Babel/ESLint integration, while Vite works out-of-the-box.
β Chose Zustand over Redux and Context API because:
- Minimal boilerplate compared to Redux.
- Better performance than Context API, which can cause unnecessary re-renders.
- Built-in asynchronous state management for API calls.
β React Router 7 was used instead of Next.js routing because:
- The project does not require SSR (Server-Side Rendering).
- More flexibility for client-side routing.
- Tree-shaking optimizations (only
BrowserRouter
is included).
β A scroll position row-based virtualization approach was chosen because:
- Only visible images are rendered, reducing memory consumption.
- Precomputed image positions prevent layout shifts.
- CSS Grid (
grid-auto-flow: dense
) ensures responsive layout.
β A 500ms debounce mechanism was used instead of immediate API calls:
- Reduces excessive network requests while typing.
- Improves user experience with real-time search feedback.
β Manual chunking in Vite was implemented to reduce initial load times:
- Splitting large dependencies (
react-router
,zustand
, etc.) into separate files. - Vendor files (
vendor.js
) cached for a year to optimize repeat visits. - Versioned filenames (
[name].[hash].js
) prevent stale cache issues.
β Strict TypeScript settings were enabled to:
- Prevent runtime errors before they happen.
- Ensure API responses are properly typed.
- Improve developer experience with auto-completion and type safety.
These design choices optimize performance, maintainability, and scalability while ensuring a smooth user experience. π
- Component rendering (SearchInput, MasonryGrid)
- Zustand store API request handling
- React Router navigation validation
- Ensures TypeScript correctness before building
- Runs unit tests before deployment
To run tests manually:
npm test
This project is deployed using GitHub Actions and served via GitHub Pages.
-
Run the production build:
npm run build
-
Deploy using GitHub Actions:
git push origin main
GitHub Actions will automatically build and deploy.
This project is licensed under the MIT License.