Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Sustainability in Web Development - How to Opti...

Sustainability in Web Development - How to Optimize React Apps?

- Efficient Data Fetching
- CSR vs SSR in carbon footprint
- Reducing Bundle Size
- Code Splitting, Image Optimization, Tree Shaking, Dependency reduction
- Reducing number of Renders
- useMemo, useCallback, React.Memo
- Monitoring performance and tools
- Chrome Dev Tools
- React DevTools
- Next.js Bundle Analyzer

Jussi Pohjolainen

January 06, 2025
Tweet

More Decks by Jussi Pohjolainen

Other Decks in Technology

Transcript

  1. Key Points • Bundle Size and Network Usage • Green

    Cloud Services • Frontend Optimization • Backend Optimization • Monitoring and Analytics
  2. Bundle Size and Network Usage • Smaller Bundles: Smaller files

    require less energy for transmission and storage. This reduces the load on data centers, networks, and user devices. • Efficient Network Usage: Optimizing data transfer reduces the energy demand of the network infrastructure, including routers, switches, and undersea cables. • Use lazy loading and tree-shaking. • Optimize images and serve modern formats (e.g., WebP/AVIF). • Implement code splitting. • Use Content Delivery Networks (CDNs) for localized distribution.
  3. Green Cloud Services • Green Cloud Providers • Use cloud

    providers committed to renewable energy and carbon neutrality (e.g., AWS with its sustainability initiatives, Google Cloud, or Azure with green energy offsets). • Energy Efficiency • Choose data centers near your user base to reduce latency and energy used for data transmission.
  4. Frontend Optimization • Design lightweight websites that load quickly and

    reduce CPU usage on user devices, especially mobile. • Static Sites: Consider static site generation (e.g., Next.js, Hugo) to reduce server load. • Caching: Leverage browser caching and service workers for offline support. • Dark Mode: Reduce energy use, particularly on OLED screens. • Minimalist Design: Simplify interfaces to use fewer assets.
  5. Backend Optimization • Server Efficiency: Optimize API calls and avoid

    redundant requests. • Database Queries: Minimize heavy queries and cache results where appropriate. • Serverless Architectures: Dynamically allocate resources to avoid always-on server energy consumption.
  6. Monitoring and Analytics • Use tools like Website Carbon Calculator

    to measure and track your site's carbon footprint. • Chrome Dev Tools • Next.js analyzer
  7. In Overall • Energy Efficiency: Optimize code, server infrastructure, and

    asset delivery to minimize energy consumption and carbon emissions. • Performance Optimization: Build fast-loading, resource-efficient websites using techniques like caching, compression, and lazy loading. • Sustainable Hosting: Choose hosting providers powered by renewable energy or with carbon offset programs. • https:!//www.thegreenwebfoundation.org/tools/directory/ • Inclusive and Minimal Design: Create accessible, lightweight websites optimized for all devices and internet speeds.
  8. Key Areas for Optimization 1. Efficient data fetching (SSR and

    CSR) 2. Reducing bundle size 3. Reducing number of renders 4. Monitoring performance using tools
  9. CSR Step Interaction Description 1. Browser -> Server Request HTML

    + JS The browser sends a request to the server for the initial page load. 2. Server -> Browser Sends HTML Skeleton + JS The server responds with a minimal HTML file and associated JavaScript files. 3. Browser Loads and Executes JavaScript The browser parses the HTML, downloads, and executes JavaScript files. 4. Browser -> API Fetches Data The JavaScript running in the browser makes API calls to fetch dynamic data. 5. API/Backend -> Browser Sends Data Response The backend API responds with the requested data in JSON or other formats. 6. Browser Renders Content The JavaScript dynamically updates the DOM using fetched data.
  10. SSR Step Interaction Description 1. Browser -> Server Request HTML

    The browser sends a request to the server for the page. 2. Server -> Browser Sends Fully Rendered HTML The server responds with the HTML content already rendered. 3. Browser Parses and Displays HTML The browser parses the HTML and displays the fully rendered page. 4. Browser -> Server Downloads JS (for hydration) The browser requests JavaScript files for hydration (making the app interactive). 5. Browser Executes JS (Hydrates App) The browser executes JavaScript to bind event listeners and make the app interactive.
  11. Client Side Rendering (CSR) • Increases time to interactive (TTI)

    since data is fetched after the initial page load. • Poor SEO (Search Engine Optimization) because search engines may not execute JavaScript to fetch the data. • Higher client-side resource usage, affecting performance on slower devices. • Longer data fetching times on the client can lead to increased energy usage on end-user devices
  12. Basic Client Side Rendering import { useEffect, useState } from

    "react"; export default function Home() { const [data, setData] = useState([]); useEffect(() !=> { fetch('https:!//example.com/data.json') .then((res) !=> res.json()) .then((json) !=> setData(json)); }, []); return ( <div> <h1>Data List!</h1> <ul> {data.map((item, index) !=> ( <li key={index}>{item.name}!</li> ))} !</ul> !</div> ); }
  13. Server Side Rendering (SSR) • Improves time to interactive (TTI)

    since the data is already rendered when page loads • Better SEO as search engines, caa crawl pre-rendered content • Reduces the load on the client-side, improving performance on low-end devices
  14. Tooling: Vite (React) • Primary Use Case: Fast, modern development

    experience for single-page applications (SPAs). • Focus: Vite is a build tool designed to replace Webpack for front-end projects. It provides a highly optimized development environment with lightning-fast hot module replacement (HMR). • Use Case: Ideal for small to medium SPAs or projects where you want full control over the project structure and configuration. • Key Feature: Extremely fast due to its use of native ES modules in development and pre-bundling using esbuild. • Setup: Minimal and unopinionated. It focuses on speed and leaves the choice of additional tools and libraries up to the developer.
  15. Tooling: Next.js • Primary Use Case: Production-ready framework for server-rendered

    and static websites. • Focus: Next.js is a React framework offering opinionated conventions and built-in features like server-side rendering (SSR), static site generation (SSG), and API routes. • Use Case: Best for large-scale projects, content-heavy websites, or applications requiring advanced routing, SSR, SSG, or backend functionality. • Key Feature: Comes with a built-in development server that supports SSR, SSG, and client-side rendering (CSR). • Setup: Opinionated and comprehensive, providing everything out of the box for a complete full-stack solution.
  16. Comparison Feature Next.js Vite Ease of Use Very easy; minimal

    setup needed. Requires manual setup for SSR. Routing Built-in file-based routing. Requires custom routing setup. Production Readiness Built-in optimizations and deployments. Requires manual optimization. Development Speed Fast but slightly slower than Vite. Extremely fast dev server. Customizability Limited to framework conventions. Highly customizable. Community Support Large, active community. Smaller but growing rapidly. Streaming SSR Fully supported out-of-the-box. Supported but needs configuration. Use Case Best for medium to large-scale apps. Best for lightweight/customized apps.
  17. App Router Enables React 18 server components, new routing mechanism,

    new pattern for fetch() and replaces the "older" methods like getServerSideProps
  18. Next.js: SSR export async function getServerSideProps() { const res =

    await fetch('https:!//example.com/data.json'); const content = await res.json(); const props = { props: { data: content } } return props; } export default function Home({ data }) { return ( <div> <h1>Data List!</h1> <ul> {data.map((item, index) !=> ( <li key={index}>{item.name}!</li> ))} !</ul> !</div> ); } getServerSideProps i s a special Next.js function used to fetch data on the server side for each request to the page. App Router is disabled It runs on the server at request time and passes the fetched data as props to the React component. this is passed as a props to the component
  19. React 18 (and Next.js): SSR export default async function Home()

    { const res = await fetch('https:!//example.com/data.json'); const data = await res.json(); return ( <div> <h1>Data List!</h1> <ul> {data.map((item, index) !=> ( <li key={index}>{item.name}!</li> ))} !</ul> !</div> ); } When component is async and it becomes ssr component Does not use browser- specific logic like useState, useE=ect
  20. Key features of SSR • Data Fetching at Request Time

    • Every time a user requests the page, getServerSideProps runs on the server. • This ensures the page always displays the latest data. • SEO Benefits • The page is pre-rendered with the fetched data on the server. • Search engines can index the fully rendered HTML, improving SEO. • Secure Fetching • API calls or sensitive logic (like using API keys) are handled securely on the server.
  21. Developer Control over SSR Optimization • SSR gives developers greater

    control over the application's infrastructure compared to CSR, which offloads rendering entirely to client devices. • CSR: The carbon footprint depends mainly entirely on the user's browser and device.
  22. Real-World Considerations • Global Traffic: For international users, SSR can

    increase the carbon footprint if the server is centralized and far from the user. Using edge servers mitigates this. • High-Load Applications: SSR's carbon footprint increases with traffic, making caching and scaling critical for reducing environmental impact. • Device Impact in CSR: CSR shifts the rendering workload to client devices, which may be inefficient for lower-end or battery-powered devices, leading to higher energy consumption per user.
  23. SSR vs CSR • SSR is generally better for reducing

    the carbon footprint in scenarios where: • The application serves static or semi-dynamic content. • Users rely on low-powered devices or poor internet connections. • Fast, efficient data delivery is critical. • CSR may be more efficient in scenarios where: • The application involves complex interactivity or frequent data updates. • The user base primarily uses high-performance devices. • Client-side caching can significantly reduce repeated server requests. • For sustainability, a hybrid approach (e.g., Static Site Generation or Server- Side Rendering with Hydration) often provides the best balance, leveraging the strengths of both SSR and CSR.
  24. Key Areas for Optimization 1. Efficient data fetching (SSR and

    CSR) 2. Reducing bundle size • Code Splitting • Image Optimization • Tree Shaking • Reduce Dependencies 3. Reducing number of renders 4. Monitoring performance using tools
  25. Why Bundle Size Matters for Carbon Footprint • Data Transfer

    Energy Usage: Data transfer requires energy, and smaller bundles mean less data being transferred. This reduces energy usage in: • User Devices: Less processing power needed to parse and render the content. • Data Centers: Reduced bandwidth and cooling energy requirements. • Improved Device Longevity: • Efficient websites cause less strain on end-user devices, extending their lifespan by reducing unnecessary processing and heat generation. • Better User Experience: • Faster load times reduce bounce rates, encouraging sustainable practices like delivering only the necessary resources.
  26. Reducing Bundle Size in React • Code Splitting • Image

    Optimization • Tree Shaking • Reduce Dependencies
  27. Default Behaviour • In a typical React application, all components

    are bundled together into one or more JavaScript files (called bundles) during the build process. • When a user accesses the application, the browser downloads the entire bundle upfront, including components that might not be immediately needed.
  28. Behaviour with Lazy Loading • When you use React.lazy to

    lazy-load a component, its code is excluded from the initial bundle.Instead, the browser only fetches the code for the lazy-loaded component when the component is about to be rendered in the app. • This reduces the initial load time of the application because only the essential components are loaded upfront. • Imagine you have a page with two sections: Home and Settings. • The Home section is displayed by default when the app loads. • The Settings section can be accessed by clicking a button. • With lazy loading, settings is not downloaded by default
  29. Benefits of Lazy Loading • Performance: • Reduces the initial

    download size of the application. • Improves the app's load time, especially for large applications. • Efficiency: • Saves network bandwidth by not loading unused components or pages. => Carbon Footprint • Ideal for applications with rarely-used features or components. • User Experience: • Users interact with the app sooner, as the essential features load quickly. • The "loading..." fallback provides a smooth transition for the lazy-loaded components.
  30. !// Home.js export default function Home() { return <h1>Welcome to

    the Home Page!!</h1>; } !// Settings.js export default function Settings() { return <h1>Settings Page!</h1>; } !// App.js import React, { useState } from "react"; import Home from "./Home"; import Settings from "./Settings"; function App() { const [showSettings, setShowSettings] = useState(false); return ( <div> <button onClick={() !=> setShowSettings(!showSettings)}> Toggle Settings !</button> {showSettings ? <Settings !/> : <Home !/>} !</div> ); } export default App; Both Home.js and Settings.js are imported statically at the top of App.js. During the build process, the code for both components is bundled into the initial JavaScript file.
  31. !// Home.js export default function Home() { return <h1>Welcome to

    the Home Page!!</h1>; } !// Settings.js export default function Settings() { return <h1>Settings Page!</h1>; } !// App.js import React, { useState, Suspense } from "react"; !// Lazy load the Settings component const Settings = React.lazy(() !=> import("./Settings")); function App() { const [showSettings, setShowSettings] = useState(false); return ( <div> <button onClick={() !=> setShowSettings(!showSettings)}> Toggle Settings !</button> <Suspense fallback={<div>Loading!!...!</div>}> {showSettings ? <Settings !/> : <Home !/>} !</Suspense> !</div> ); } export default App; The Home component is statically imported, so it is included in the initial bundle and available immediately. The Settings component is not included in the initial bundle because it is lazily loaded using React.lazy. While the browser fetches the Settings component, the <div>Loading...</div> specified in the Suspense component is displayed.
  32. Lazy Loading in Next.js • React.lazy is designed for client-side

    rendering, it does not SSR • Dynamic import for components support CSR and SSR • Automatic route-based code splitting • Lazy loading of images using <Image> component
  33. Next.js: Dynamic import for components • React.lazy • Designed for

    client-side rendering only. It does not support server-side rendering (SSR). • If you use React.lazy in a Next.js app that uses SSR, the server will not pre- render the lazy-loaded component, potentially breaking SSR or returning an empty page. • Next.js dynamic • Supports both SSR and client-side rendering (CSR). • You can configure it to enable or disable SSR for a specific component using the ssr option.
  34. Next.js dynamic vs React.lazy Feature React.lazy Next.js dynamic Where it

    can be used Always outside the component. Can be used both inside and outside the component. Supports SSR No, client-side only. Yes, if ssr: true is specified. Conditional Imports Not possible. Possible (e.g., inside a button click handler). Fallback Rendering Requires Suspense for fallback. Customizable fallback via the loading option. Flexibility Limited to static, client-side usage. Highly flexible: supports SSR, CSR, and runtime decisions.
  35. import dynamic from "next/dynamic"; import { useState } from "react";

    export async function getServerSideProps() { const response = await fetch("https:!//jsonplaceholder.typicode.com/users"); const data = await response.json(); return { props: { customers: data, }, }; } console.log("app loaded"); export default function App({ customers }) { const [showTable, setShowTable] = useState(false); const CustomerTable = dynamic(() !=> import("!../components/CustomerTable"), { ssr: false, !// Disable SSR loading: () !=> <p>Loading customer data!!...!</p>, }); return ( <div> <h1>Customer Information!</h1> <button onClick={() !=> setShowTable((prev) !=> !prev)}> {showTable ? "Hide Customers" : "Show Customers"} !</button> {showTable !&& <CustomerTable data={customers} !/>} !</div> ); }
  36. console.log("Table loaded"); function CustomerTable({ data }) { if (!Array.isArray(data) !||

    data.length !!=== 0) { return <p>No customer data available.!</p>; } return ( <table border="1" cellPadding="5"> <thead> <tr> <th>ID!</th> <th>Name!</th> <th>Email!</th> <th>Phone!</th> !</tr> !</thead> <tbody> {data.map((customer) !=> ( <tr key={customer.id}> <td>{customer.id}!</td> <td>{customer.name}!</td> <td>{customer.email}!</td> <td>{customer.phone}!</td> !</tr> ))} !</tbody> !</table> ); } export default CustomerTable;
  37. !// App.js import React, { useState } from "react"; import

    dynamic from "next/dynamic"; import Main from "!../components/Main"; export async function getServerSideProps(context) { console.log("SSR"); const showTable = context.query.showtable !!=== "true"; let customers = []; if (showTable) { console.log("Fetch api"); const response = await fetch("https:!//jsonplaceholder.typicode.com/users"); customers = await response.json(); } return { props: { customers, initialShowTable: showTable, }, }; } console.log("Home loaded"); function Home({ customers, initialShowTable }) { const [showTable, setShowTable] = useState(initialShowTable); const CustomerTable = dynamic(() !=> import("!../components/CustomerTable"), { ssr: true, loading: () !=> <p>Loading customer data!!...!</p>, }); return ( <div> <h1>Customer Information!</h1> {showTable ? <CustomerTable data={customers} !/> : <Main !/>} !</div> ); } export default Home; ssr is true conditional fetching
  38. Next.js: Lazy Loading Routes • Next.js automatically performs route-based code

    splitting, meaning each page is bundled separately. • When a user navigates to a new route, the corresponding JavaScript for that page is fetched dynamically.
  39. Next.js: Routing • Each file in the pages/ directory corresponds

    to a route. • For example: • pages/index.js → / • pages/about.js → /about • pages/contact.js → /contact • You don’t need a separate router configuration like in React apps with react-router-dom.
  40. Example: pages/page1.js export default function Page1() { return ( <div>

    <h1>Welcome to Page 1!</h1> <p>This is the content of Page 1.!</p> !</div> ); }
  41. Best practice: Separate components • Reusability: Components can be reused

    across multiple pages or other components. • Readability: Smaller components make your code easier to read, maintain, and debug. • Separation of Concerns: Keeps your pages focused on routing and layout while moving reusable logic and presentation to components.
  42. pages/page1.js import Message from '!../components/Message'; export default function Page1() {

    return ( <div> <h1>Welcome to Page 1!</h1> <Message text="This is a reusable component!" !/> !</div> ); }
  43. App Router • The App Router in Next.js (introduced in

    version 13) is a new paradigm for structuring applications using React Server Components (RSC) • It is an alternative to the traditional Pages Router, enabling a more flexible and modular approach to building applications.
  44. Key Features of the App Router • React Server Components

    (RSC) • Allows components to run on the server by default, reducing client-side JavaScript and improving performance. • Components in the App Router are server-rendered unless explicitly marked as client components. • File-Based Routing with Nested Layouts • Introduces nested folder-based routing with support for colocating layouts, pages, and components. • url1/page.js, url2/page.js • Data Fetching at the Component Level • Provides built-in data fetching methods (e.g., fetch) directly in React Server Components.
  45. app/posts/page.js import { Suspense } from 'react'; import PostList from

    './PostList'; export default function PostsPage() { return ( <div> <h2>Posts!</h2> <Suspense fallback={<p>Loading posts!!...!</p>}> <PostList !/> !</Suspense> !</div> ); }
  46. app/posts/PostList.js async function fetchPosts() { const response = await fetch('https:!//jsonplaceholder.typicode.com/posts');

    if (!response.ok) { throw new Error('Failed to fetch posts'); } return response.json(); } export default async function PostList() { const posts = await fetchPosts(); return ( <ul> {posts.slice(0, 5).map(post !=> ( <li key={post.id}> <h3>{post.title}!</h3> <p>{post.body}!</p> !</li> ))} !</ul> ); }
  47. SSR on by default • In the Next.js App Router,

    components are server components by default • sent as HTML to the client • To explicitly mark a component as a client component, you need to add the special directive "use client" at the top of the file. • Client components can use React hooks (e.g., useState, useEffect) and interact with browser APIs (like localStorageor window).
  48. Reducing Bundle Size in React • Code Splitting • Image

    Optimization • Tree Shaking • Reduce Dependencies
  49. Image Optimization in React • Next-gen Formats: Use modern formats

    like WebP, which provide better compression and quality compared to traditional formats like JPEG or PNG. • SVG for Vector Graphics: Use SVG for icons and simple graphics, as they scale well and have smaller file sizes. • Use the loading="lazy" attribute in <img> tags. • Libraries like React Lazy Load or built-in features in frameworks like Next.js (next/image) can simplify this process. • Serve appropriately sized images based on the user's device and screen resolution.
  50. Native Lazy Loading • Example • <img src="image.webp" alt="description" loading="lazy"

    !/> • Libraries like React Lazy Load or built-in features in frameworks like Next.js (next/image) can simplify this process.
  51. Use Responsive Images • Serve appropriately sized images based on

    the user's device and screen resolution. • Use the srcset attribute for <img> tags to define multiple image sizes. • Tools like sharp (Node.js library) can generate different image sizes during the build process.
  52. Common Responsive Design Breakpoints • Extra small devices: max-width: 575px

    (phones). • Small devices: 576px to 767px (small tablets). • Medium devices: 768px to 991px (larger tablets). • Large devices: 992px to 1199px (laptops). • Extra-large devices: 1200px+ (desktops).
  53. HTML5.1: Example <img srcset=" images/image_1_575px.webp 575w, images/image_1_768px.webp 768w, images/image_1_992px.webp 992w,

    images/image_1_1200px.webp 1200w, images/image_1_1600px.webp 1600w " sizes=" (max-width: 575px) 575px, (max-width: 768px) 768px, (max-width: 992px) 992px, (max-width: 1200px) 1200px, 1600px " alt="Responsive Example" loading="lazy" !/> Define the width of the image in pixels, so 575w => 575px if viewport width is less than 575px then.. .. the browser assumes that image will take up to 575 CSS pixels of space
  54. Scenario 1: Viewport Width = 500px, DPR = 1 •

    max-width: 575px applies because 500px ≤ 575px. • The browser assumes the image will occupy 575 CSS pixels. • Required resolution = 575px × 1 = 575px. • The browser fetches the 575w image from the srcset.
  55. Scenario 1: Viewport Width = 500px, DPR = 2 •

    max-width: 575px applies because 500px ≤ 575px. • The browser assumes the image will occupy 575 CSS pixels. • Required resolution = 575px × 2 = 1150px. • The browser fetches the 1200w image from the srcset, 992w is too small
  56. HTML5.1: Example <img src="small.jpg" srcSet="small.jpg 480w, medium.jpg 800w, large.jpg 1200w"

    sizes="(max-width: 600px) 480px, (max-width: 1200px) 800px, 1200px" alt="A beautiful landscape" loading="lazy" !/> For users who do not scroll to the bottom of the page, images that remain off-screen are never loaded. Pages with many images (e.g., galleries, news articles, or e- commerce listings) benefit the most from lazy loading. Users can start interacting with the page content (text, buttons, links) sooner, as the browser prioritizes visible elements over off-screen images.
  57. Optimize with Framework Features • If you're using a React

    framework like Next.js, take advantage of its built-in <Image> component: • Automatically optimizes images. • Generates responsive sizes. • Supports lazy loading and WebP.
  58. Next.js: Lazy Loading with <Image> • Lazy loading images in

    Next.js can be efficiently achieved using the <Image> component, which is part of Next.js's built-in image optimization features. • The <Image> component automatically supports lazy loading by default, ensuring that images are only loaded when they enter the viewport. • This improves page load performance, reduces bandwidth usage, and enhances user experience.
  59. <Image> Key Features • Lazy Loading by Default: Images are

    lazily loaded, meaning they are not downloaded by the browser until they are about to be visible on the user's screen. • Automatic Optimization: The <Image> component optimizes images on the server side, delivering them in formats like WebP when supported, and resizing images to fit the specified dimensions. • Placeholder Support: You can define a placeholder (blur or empty) to show before the image loads, further enhancing the visual experience. • Images are cached and served for subsequent requests, reducing server workload.
  60. WebP • WebP is a modern image format developed by

    Google that provides superior lossless and lossy compression for images on the web • Designed to reduce image file sizes while maintaining high visual quality, thereby improving website performance by reducing page load times and bandwidth usage
  61. WebP: Key Features • WebP images are typically 25–34% smaller

    than JPEG images and up to 26% smaller than PNG images of comparable quality. • WebP supports transparency (alpha channel) in both lossless and lossy modes, unlike JPEG. • WebP supports animated images, providing a more efficient alternative to formats like GIF. • Most modern browsers (Chrome, Firefox, Edge, and Safari 14+) support WebP. However, fallback mechanisms may be needed for older browsers.
  62. Tools • Command-Line Tools: Use ImageMagick or cwebp for powerful,

    scriptable workflows. • Automated Workflows: Use sharp (Node.js) or Pillow (Python) for integration into development pipelines. • One-Off Conversions: Use GUI tools like XnConvert or online tools like Squoosh for occasional use.
  63. Next.js <Image> Example import Image from 'next/image'; export default function

    Example() { return ( <div> <h1>Lazy Loading Example!</h1> <Image src="/example-image.jpg" !// Path to the image alt="A beautiful example" width={500} !// Set the desired width height={300} !// Set the desired height !/> !</div> ); }
  64. Next.js <Image> Placeholder <Image src="/example-image.jpg" alt="A beautiful example" width={500} height={300}

    placeholder="blur" blurDataURL="/example-image-blur.jpg" !// Low-res or base64 version of the image !/>
  65. Benefits of Using <Image> for Lazy Loading • Improved Performance:

    Unnecessary images are not loaded upfront, reducing initial page load time. • SEO Friendly: The alt attribute ensures accessibility and improves SEO. • Responsive Design: The <Image> component works seamlessly with responsive layouts, adapting to different screen sizes. • Out-of-the-box Support: Next.js handles all the complex parts, such as creating optimized image formats, implementing lazy loading, and adding responsive behavior.
  66. <Image> and Responsive Design • The <Image> component can automatically

    adjust its size to match the parent container, making it fit dynamically within responsive layouts. • Next.js automatically generates multiple versions of the image at different resolutions (e.g., 1x, 2x, etc.) and serves the appropriate one based on the user’s screen resolution. • If the image is rendered on a device with a Retina display, the <Image> component ensures a higher-resolution version of the image is used, improving clarity without requiring manual intervention.
  67. srcSet with <Image> import Image from 'next/image'; export default function

    ResponsiveImage() { return ( <Image src="/example-image.jpg" !// Base image alt="A beautiful landscape" width={1600} !// Largest image width height={900} !// Corresponding height sizes="(max-width: 480px) 480px, (max-width: 800px) 800px, 1200px"!/> ); } Automatically generate multiple versions of example-image.jpg at various resolutions (e.g., 480w, 1024w, etc.).
  68. Reducing Bundle Size in React • Code Splitting • Image

    Optimization • Tree Shaking • Reduce Dependencies
  69. Tree Shaking • Tree shaking is a technique used to

    eliminate dead code—unused parts of the codebase—from the final bundle during the build process. • This optimization reduces the size of the JavaScript bundle that is delivered to the user, which can improve performance by speeding up loading times and reducing resource usage.
  70. How Tree Shaking Works • Modern JavaScript tools, like Webpack,

    Rollup, and Parcel, use static analysis to inspect the dependency graph of the project. • They analyze the import and export statements in ES6 modules to determine which parts of the code are actually used. • Code that is not referenced in the application (i.e., not "reachable" during execution) is considered "dead code" and is removed from the final bundle. • After tree shaking, the resulting code is further minified by tools like Terser or UglifyJS to reduce its size.
  71. In React Context • React Components: Tree shaking can help

    exclude unused React components if they are modularized properly (i.e., exported and imported correctly). • For example, if your project only uses Button from a library but not Card, tree shaking can exclude Card if the library supports tree-shakeable exports. • Utility Functions: Similarly, utility functions or hooks that are imported but not used in the project will be removed during tree shaking.
  72. Prerequisites for Tree Shaking • ES Modules (ESM) • Properly

    Configured Build Tools: • Tools like Webpack or Rollup need to be setup up. Usually on by default. • Library Support • The libraries and dependencies you use must be designed to support tree shaking, which means they need to export individual modules instead of bundling everything into a single export.
  73. Example in React • Tree-shakeable import • import debounce from

    'lodash/debounce'; • Not tree-shakeable • import _ from 'lodash';
  74. Tree shaking in Next.js • Tight Integration with Webpack or

    Turbopack • Webpack • Mature, well-supported, feature-rich, stable, reliable • Slow build times, complex configuration • Turbopack (The Future of Next.js) • Claims to be 700x faster then Webpack during development for large apps • Tree shaking and other optimizations are faster • Developed by Vercel team who has done Next.js • Simpler Configuration • Future-proof • Early stage!
  75. Next.js: Turbopack • In Turbopack, tree-shaking is designed to work

    automatically with minimal input from the developer • Turbopack optimizes the bundling process by making tree-shaking an integral part of its architecture. • Turbopack performs static analysis on your codebase, focusing on ESModules (ESM). • It identifies and removes code that is not imported or referenced in the dependency graph. • It uses the same principles as Webpack but implements them much faster and more efficiently, thanks to its Rust-based architecture. • Turbopack automatically detects unused code (dead code) and eliminates it from the final bundle. • Turbopack deeply analyzes imports from third-party libraries, ensuring that only the necessary modules are included.
  76. What developer needs to know? • Use ESM syntax correctly

    – do not use CommonJS • Choose Tree-Shakeable libraries • They use ESM and offer module exports • No dynamic imports • if(condition) import ...
  77. Reducing Bundle Size in React • Code Splitting • Image

    Optimization • Tree Shaking • Reduce Dependencies
  78. Reducing Dependencies • Reducing dependencies refers to minimizing the number

    and size of external libraries or packages included in your project • Dependencies are third-party modules or libraries installed via npm (or similar package managers) • Each dependency contributes to the final size of your application bundle • A larger bundle can slow down page load times, especially on slower networks or devices.
  79. Benefits • Smaller bundle size: Every dependency added to your

    project increases the JavaScript code that browsers need to download and execute, potentially leading to slower performance. • Reduced attack surface: Fewer dependencies mean fewer opportunities for security vulnerabilities. • Better maintainability: With fewer libraries, there’s less risk of version conflicts and outdated or unsupported code. • Carbon Footprint
  80. Strategies • Evaluate whether you really need a specific library.

    Often, functionality can be implemented natively in JavaScript or with a small utility function. • Example: Instead of adding a library like lodash for a single utility function, use native JavaScript methods like Array.map, Object.assign, etc. • Ensure the libraries you include support tree-shaking, which removes unused code during the build process. • Use smaller, modular libraries • date-fns over moment.js? • Use tools like Webpack Bundle Analyzer or Vite's visualizer plugin to identify large dependencies in your project • Use tools like depcheck or manually check package.json for unused or outdated dependencies and remove them.
  81. Next.js and Bundle size Feature Description Automatic Tree-Shaking Removes unused

    JavaScript from libraries. Code Splitting Splits code by route, reducing initial load. Dynamic Imports Load components or libraries on demand. Optimized Images Reduces image sizes with modern formats. Server-Side Rendering Reduces JavaScript required on the client. Static Site Generation Minimizes client-side computation. Bundle Analyzer Identifies large dependencies. External Dependencies Loads heavy libraries via CDN.
  82. Key Areas for Optimization 1. Efficient data fetching (SSR and

    CSR) 2. Reducing bundle size 3. Reduce number of renders - useMemo, useCallback, React.Memo 4. Monitoring performance using tools
  83. useMemo and useCallback hooks • In React, useMemo and useCallback

    are hooks that help optimize performance by preventing unnecessary computations and re-renders • By reducing these inefficiencies, you indirectly lower the carbon footprint of your application by using fewer computational resources (CPU, memory, etc.), which in turn reduces energy consumption.
  84. useMemo • useMemo is used to memoize the result of

    an expensive computation so that it is only recomputed when its dependencies change. • const memoizedValue = useMemo(() !=> computeExpensiveValue(a, b), [a, b]); • For expensive computations like sorting, filtering, or large array transformations. • When a calculated value is repeatedly used in your component but doesn’t change frequently. • Compute and memoize a derived value. It returns a value that is cached until its dependencies change.
  85. "use client"; import React, { useState } from "react"; export

    default function FilteredListWithoutMemo({ items }) { const [search, setSearch] = useState(""); const [random, setRandom] = useState(0); const filteredItems = items.filter((item) !=> { console.log("no useMemo", "filtering!!..."); return item.includes(search); }); console.log("no useMemo", "render"); return ( <div> <button onClick={() !=> setRandom(Math.random())}>{random}!</button> <br !/> <input type="text" value={search} onChange={(e) !=> setSearch(e.target.value)} placeholder="Search items" !/> <ul> {filteredItems.map((item, index) !=> ( <li key={index}>{item}!</li> ))} !</ul> !</div> ); } This is run even if button is clicked
  86. "use client"; import React, { useState, useMemo } from "react";

    export default function FilteredList({ items }) { const [search, setSearch] = useState(""); const [random, setRandom] = useState(0); !// Start with a stable value const filteredItems = useMemo(() !=> { console.log("useMemo", "filtering"); return items.filter((item) !=> item.includes(search)); }, [items, search]); console.log("useMemo", "render"); return ( <div> <button onClick={() !=> setRandom(Math.random())}>{random}!</button> <br !/> <input type="text" value={search} onChange={(e) !=> setSearch(e.target.value)} placeholder="Search items" !/> <ul> {filteredItems.map((item, index) !=> ( <li key={index}>{item}!</li> ))} !</ul> !</div> ); } Only run if items are search is changed. If button is pressed it will us cached version of filteredItems
  87. useCallback • useCallback is a React Hook that memoizes a

    function so that it does not get re-created on every render unless its dependencies change. • useMemo memoizes a result of computation • This helps optimize performance, especially in scenarios where functions are passed as props to child components or used in useEffect. • To prevent that a new function instance is created every time the component re-renders.
  88. "use client"; import React, { useState, useCallback } from "react";

    export default function useCallback1() { const [count, setCount] = useState(0); const increment1 = useCallback(() !=> { setCount((prev) !=> prev + 1); }, []); const increment2 = () !=> { setCount((prev) !=> prev + 1); }; return ( <div> <button onClick={increment1}>Increment Count - useCallback!</button> <button onClick={increment2}>Increment Count - NO useCallback!</button> <p>Count: {count}!</p> !</div> ); } increment1 – created only once for r— renders. increment2 - created on every re- render
  89. "use client"; import React, { useState, useCallback, useRef } from

    "react"; export default function useCallback1() { const [count, setCount] = useState(0); const increment1 = useCallback(() !=> { setCount((prev) !=> prev + 1); }, []); const increment2 = () !=> { setCount((prev) !=> prev + 1); }; !// Use refs to store the previous references const previousIncrement1Ref = useRef(increment1); const previousIncrement2Ref = useRef(increment2); !// Compare the previous and current references console.log( "increment1:", previousIncrement1Ref.current !!=== increment1 ); console.log( "increment2:", previousIncrement2Ref.current !!=== increment2 ); !// Update the refs for the next render previousIncrement1Ref.current = increment1; previousIncrement2Ref.current = increment2; console.log("LargeComponent rendered"); return ( <div> <button onClick={increment1}>Increment Count - useCallback!</button> <button onClick={increment2}>Increment Count - NO useCallback!</button> <p>Count: {count}!</p> !</div> ); } increment1 – created only once for r— renders. increment2 - created on every re- render
  90. Higher-Order Component (HOC) in React • A Higher-Order Component (HOC)

    is a function that takes a component as an input and returns a new component. It is a pattern used in React to reuse component logic. • HOCs are not a feature of React itself but a design pattern that emerges from the compositional nature of React components. • const EnhancedComponent = HigherOrderComponent(BaseComponent);
  91. import React from "react"; !// Higher-Order Component function withLogging(WrappedComponent) {

    return function EnhancedComponent(props) { console.log("Props:", props); return <WrappedComponent {!!...props} !/>; }; } !// Base Component function MyComponent({ name }) { return <h1>Hello, {name}!!</h1>; } !// Enhanced Component const MyComponentWithLogging = withLogging(MyComponent); export default function App() { return <MyComponentWithLogging name="John" !/>; }
  92. React.memo? • React.memo is a higher-order component (HOC) in React

    that memoizes a functional component. • It prevents the component from re-rendering unnecessarily by skipping renders when its props haven’t changed. • By default, React re-renders child components whenever a parent re- renders, even if the child's props are the same. • When you wrap a functional component with React.memo, React shallowly compares the component's new and previous props. • const MemoizedComponent = React.memo(MyComponent);
  93. "use client"; import React, { useState, memo } from "react";

    function Child({ count }) { console.log("Child component rendered"); return <p>Child Count: {count}!</p>; } const ChildComponentMemorizied = React.memo(Child); function Parent() { const [count, setCount] = useState(0); const [otherState, setOtherState] = useState(0); console.log("Parent component rendered"); return ( <div> <button onClick={() !=> setCount((prev) !=> prev + 1)}> Increment Count !</button> <button onClick={() !=> setOtherState((prev) !=> prev + 1)}> Update Other State !</button> <ChildComponentMemorizied count={count} !/> !</div> ); } When other state changes, child is not re-rendered!
  94. React.memo and useCallback? • useCallback comes into play when you

    pass functions as props to a child component wrapped with React.memo • Even if the logic of the function remains the same, React creates a new function reference on every render • This causes React.memo to treat the function prop as "changed," resulting in unnecessary re-renders of the child component.
  95. Child function Child({ count, onClick }) { console.log("Child component rendered");

    return ( <div> <p>Child Count: {count}!</p> <button onClick={onClick}>Say Hello From Child!</button> !</div> ); } const ChildComponentMemorized = React.memo(Child);
  96. function Parent() { const [count, setCount] = useState(0); const [otherState,

    setOtherState] = useState(0); !// Function defined inline (re-created on every render) const handleClick = () !=> { alert("Hello from child component"); }; console.log("Parent component rendered"); return ( <div> <button onClick={() !=> setCount((prev) !=> prev + 1)}> Increment Count !</button> <button onClick={() !=> setOtherState((prev) !=> prev + 1)}> Update Other State {otherState} !</button> <ChildComponentMemorized count={count} onClick={handleClick} !/> !</div> ); } New function instance in every render Passing new props on every render, child re-renders
  97. function Parent() { const [count, setCount] = useState(0); const [otherState,

    setOtherState] = useState(0); !// Function defined inline (re-created on every render) const handleClick = useCallback(() !=> { alert("Hello from child component"); }, []); console.log("Parent component rendered"); return ( <div> <button onClick={() !=> setCount((prev) !=> prev + 1)}> Increment Count !</button> <button onClick={() !=> setOtherState((prev) !=> prev + 1)}> Update Other State {otherState} !</button> <ChildComponentMemorized count={count} onClick={handleClick} !/> !</div> ); } Same function instance in every render Passing same props on every render, child does not re- render
  98. Key Areas for Optimization 1. Efficient data fetching (SSR and

    CSR) 2. Reducing bundle size 3. Reduce number of renders 4. Monitoring performance using tools
  99. Tools for Analysis • Chrome DevTools • Lighthouse for performance

    metrics • Network tab for analyzing requests • React DevTools • Identifying unnecessary re-renders • Next.js Bundle Analyzer • Monitoring and optimizing bundle size
  100. Report: Performance metrics • Speed Index - How quickly content

    is visually displayed during loading • First Contentful Paint (FCP) • Largest Contentful Paint (LCP) • Time to Interactive (TTI) • Cumulative Layout Shift (CLS) • Total Blocking Time (TBT) • Time to Interactive (TTI)
  101. Energy Impact • Less Processing Power • Faster-loading pages reduce

    CPU and GPU usage, minimizing energy consumption on both the client device (user’s browser) and the server. • Fewer Resources Downloaded • Optimizing images, scripts, and other assets reduces bandwidth usage, lowering energy demands in data centers and network infrastructure. • Reduced Idle Time • Efficient performance avoids unnecessary delays, allowing devices to return to low-power or idle states sooner.
  102. Performance Metrics Metric Weight Largest Contentful Paint 25 % Total

    Blocking Time 25 % Cumulative Layout Shift 15 % Speed Index 15 % First Contentful Paint 10 % Time to Interactive 10 %
  103. Network Tab • Filter requests • Use filters like XHR

    (AJAX requests), JS, CSS, Img, etc., to narrow down specific resources. • Columns • Name, status, type, size, time, waterfall • Throttling • Simulate slower connections (e.g., "Fast 3G" or "Offline") from the dropdown menu to test performance under varying conditions.
  104. React Dev Tools • React DevTools is a browser extension

    (available for Chrome and Firefox) that allows you to inspect and debug the component tree of a React application • It provides insights into the structure of your app, state, props, and component performance.
  105. Components tab • Inspect Component Tree • View Props and

    State • Edit Props or State (For Debugging)
  106. Profiler tab • Record Renders • Click "Start profiling" to

    record interactions and renders • It shows a timeline of rendering activity • Analyze Performance • Identify which components take the most time to render. • Look for unnecessary re-renders (components rendering when they don’t need to). • Optimize Rendering • Use the Profiler data to optimize expensive components
  107. Next.js Bundle Analyzer • The Next.js Bundle Analyzer is a

    tool that helps you visualize the size and structure of your Next.js application’s JavaScript bundles. • Generates an interactive treemap of your bundles, allowing you to identify large or unnecessary dependencies and optimize your app's performance
  108. Why Use Next.js Bundle Analyzer? • Identify Large Dependencies •

    Find out which libraries or modules are contributing the most to your bundle size. • Optimize Performance: • Reducing the bundle size improves loading times, especially for users on slower networks or devices. • Debug Code Splitting: • Visualize how your code is split into chunks and ensure unnecessary code is not loaded upfront. • Detect Unused Code: • Spot unused or redundant code that can be removed to reduce bundle size.
  109. Install: npm install @next/bundle-analyzer • Configure next.config.mjs import withBundleAnalyzer from

    "@next/bundle-analyzer"; const withAnalyzer = withBundleAnalyzer({ enabled: process.env.ANALYZE !!=== "true", !// Enable analyzer only when ANALYZE=true openAnalyzer: true, !// Automatically open the report in the browser }); const nextConfig = { reactStrictMode: false, !// Other Next.js configurations }; export default withAnalyzer(nextConfig);
  110. nodejs.html • This report shows the JavaScript bundle used in

    the Node.js runtime during server-side rendering (SSR). • It includes code required to generate server-rendered pages or handle API routes. • If your app heavily relies on SSR or has API routes, this report helps you optimize server-side code and dependencies. • Useful for identifying large server-only libraries that could slow down server-side rendering.
  111. Edge Computing • Node.js server can be in Australia –

    when doing fetch api takes time if doing it from Finland • With edge computing you can spread small servers all around the world so that you can access them faster • Now if doing the connection, you connect to a small server in Finland instead of Australia • Edge is fast, but it has limitations • they can’t handle big tasks like running a full database or doing heavy calculations. • You redirect users, authentication
  112. edge.html • This report shows the JavaScript bundle used when

    deploying your app to Edge environments, such as Vercel Edge Functions or other Content Delivery Networks (CDNs). • Edge functions are often used for running server-side logic closer to the user, improving performance. • If you’re deploying your app to an edge network (e.g., Vercel, Cloudflare Workers), this report helps you identify and optimize the code that runs in the Edge runtime. • Ensure minimal and efficient dependencies to meet size and execution time limits often imposed by edge environments.
  113. client.html • This report shows the JavaScript bundle sent to

    the client-side (browser). • It includes all code required for hydration (converting server- rendered HTML into a fully interactive React app) and any other client-side logic. • This is the most critical report for optimizing frontend performance because it affects the loading speed and interactivity of your app. • Large bundles here can cause slow load times, especially on slower networks.
  114. Conclusion • nodejs.html: Represents the server-side code responsible for generating

    pages or API responses on the Node.js backend. • edge.html: Represents server-side code optimized for edge networks, where server-side logic is executed closer to the end user. • client.html: Represents client-side JavaScript that runs in the browser, responsible for interactivity, hydration, and user experience.