<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Ayantunji Timilehin - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Ayantunji Timilehin - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 16 Jun 2026 21:34:42 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/author/timmy471/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build AI Apps in the Browser with TensorFlow.js and WebGPU ]]>
                </title>
                <description>
                    <![CDATA[ Most developers think of AI the same way: you send data to a server, the server thinks, you get a response back. That mental model made sense for a long time. It still makes sense for a lot of use cas ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-ai-apps-in-the-browser-with-tensorflow-js-and-webgpu/</link>
                <guid isPermaLink="false">6a1706d0badcd8afcb00415d</guid>
                
                    <category>
                        <![CDATA[ Programming Tips ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ WebAssembly ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TensorFlow ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #chrome_devtools ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayantunji Timilehin ]]>
                </dc:creator>
                <pubDate>Wed, 27 May 2026 14:59:28 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/87141e6b-7529-4278-a2fa-ee8e4d9f9062.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most developers think of AI the same way: you send data to a server, the server thinks, you get a response back. That mental model made sense for a long time. It still makes sense for a lot of use cases.</p>
<p>But there’s a quiet shift happening inside the browser environment that a lot of engineers are completely missing out on.</p>
<p>The modern browser isn’t just a glorified engine for rendering HTML and CSS anymore. It’s turning into a full-blown runtime for local intelligence. We’ve reached a point where you can ship raw machine learning models straight to a user's device and run inference completely client-side. No server trips, no API keys to protect, and once those initial assets load, zero dependency on an internet connection.</p>
<p>This is the reality of Web AI. If you're building for the web today, understanding this paradigm shift is easily one of the most valuable skills you can add to your stack.</p>
<p>In this guide, we’re going to pull back the curtain on how Web AI actually operates under the hood, break down the browser technology stack making it possible, and build a real, working image classifier using Teachable Machine and TensorFlow.js. Along the way, we’ll also set up a live benchmark so you can watch exactly how WebGL and WebGPU stack up against each other in real-time execution speeds.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along with this tutorial, you should have:</p>
<ul>
<li><p>A working knowledge of JavaScript</p>
</li>
<li><p>Basic familiarity with HTML and how the browser works</p>
</li>
<li><p>Google Chrome installed (required for WebGPU support and Chrome's built-in AI APIs)</p>
</li>
<li><p>A code editor like VS Code with the Live Server extension installed (recommended for running the demo locally)</p>
</li>
</ul>
<p>No prior machine learning experience is required.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-what-is-web-ai">What is Web AI?</a></p>
</li>
<li><p><a href="#heading-browser-ai-vs-cloud-ai">Browser AI vs Cloud AI</a></p>
</li>
<li><p><a href="#heading-the-technology-stack">The Technology Stack</a></p>
</li>
<li><p><a href="#heading-how-to-build-ai-in-the-browser">How to Build AI in the Browser</a></p>
</li>
<li><p><a href="#heading-chromes-built-in-ai-apis">Chrome's Built-in AI APIs</a></p>
</li>
<li><p><a href="#heading-where-web-ai-is-headed">Where Web AI Is Headed</a></p>
</li>
<li><p><a href="#heading-what-you-learned">What You Learned</a></p>
</li>
<li><p><a href="#heading-resources">Resources</a></p>
</li>
</ul>
<h2 id="heading-what-is-web-ai">What is Web AI?</h2>
<p>Instead of sending data off to a distant cloud server, Web AI lets you run machine learning models directly on the user’s device inside their browser. It uses standard web tech like JavaScript, WebAssembly, and WebGPU to handle all the heavy lifting right then and there.</p>
<p>The simplest definition: <strong>intelligence that runs in the browser, without sending your data anywhere.</strong></p>
<p>Most of us already interact with on-device AI every day without realizing it. Think about unlocking an iPhone. The second you lift it, Face ID maps out roughly 30,000 infrared points, feeds that data through a neural network living on Apple's local silicon, matches it against an encrypted embedding, and opens the phone. The whole process takes milliseconds and happens entirely offline.</p>
<p>Browser-based AI works on that exact same core architecture. The only real difference is that we're building on top of shared web standards rather than native hardware APIs. When you spin up a face-tracking model using TensorFlow.js or MediaPipe in Chrome, you're running that exact same pipeline:</p>
<pre><code class="language-plaintext">Camera input → Local ML model → Local decision
</code></pre>
<p>No round trip. No server. The browser is your Neural Engine.</p>
<h2 id="heading-browser-ai-vs-cloud-ai">Browser AI vs Cloud AI</h2>
<p>There’s no right or wrong answer here. It just depends on what you’re trying to build. Both approaches have their pros and cons, so it’s just a matter of picking the tool that fits your specific use case.</p>
<table>
<thead>
<tr>
<th></th>
<th>Browser AI (Client-Side)</th>
<th>Cloud AI (Server-Side)</th>
</tr>
</thead>
<tbody><tr>
<td>Internet required</td>
<td>No</td>
<td>Yes</td>
</tr>
<tr>
<td>Latency</td>
<td>Near-zero</td>
<td>Depends on network</td>
</tr>
<tr>
<td>Privacy</td>
<td>Data stays on device</td>
<td>Data leaves the device</td>
</tr>
<tr>
<td>Model size</td>
<td>Small to medium</td>
<td>As large as you need</td>
</tr>
<tr>
<td>Cost at inference time</td>
<td>Free</td>
<td>Per token or per request</td>
</tr>
</tbody></table>
<p><strong>Use browser AI when:</strong></p>
<ul>
<li><p>You need split-second speed for things like tracking gestures or detecting objects live on a webcam</p>
</li>
<li><p>The app has to work offline (whether it's a PWA or just needs to survive spotty internet)</p>
</li>
<li><p>Privacy is a hard requirement to keep sensitive data like medical inputs, biometrics, or financial information strictly local</p>
</li>
<li><p>You want to reduce or eliminate API costs on high-frequency, lightweight predictions</p>
</li>
</ul>
<p><strong>Use cloud AI when:</strong></p>
<ul>
<li><p>You need large models like GPT-4, Gemini Pro, or Stable Diffusion</p>
</li>
<li><p>You need centralized model updates, A/B testing, or user analytics</p>
</li>
<li><p>You require serious GPU or TPU compute power</p>
</li>
</ul>
<p>Most production systems actually use a mix of both. Take Google Photos: it handles face detection right on your device so it’s fast and private, but leaves the heavier categorization work for the cloud. Or think of a modern web app that might use TensorFlow.js locally to classify images instantly, but calls the Gemini API when it needs deeper language processing.</p>
<p>This hybrid setup, keeping lightweight intelligence at the edge and heavy compute in the cloud, is usually the sweet spot for most apps.</p>
<h2 id="heading-the-technology-stack">The Technology Stack</h2>
<p>Browser AI isn’t just a single tool – it’s a stacked layer of technologies. Knowing how these layers fit together makes it a lot easier to choose your setup and navigate the trade-offs.</p>
<h3 id="heading-tensors">Tensors</h3>
<p>Before jumping into any ML framework, you need to understand tensors. Not deeply, just enough of a handle on them so you don't get blindsided by tensor shape errors, because they will happen and they can be tricky to debug.</p>
<p>Think of a tensor as a multi-dimensional grid of numbers. Whether your model is processing images, audio, or text, everything gets converted into this format first. Models only speak numbers, and tensors are the containers that hold them.</p>
<pre><code class="language-plaintext">A single number       → 0D tensor (scalar):  42
A list of numbers     → 1D tensor (vector):  [0.2, 0.8, 0.5]
A table of numbers    → 2D tensor (matrix):  [[1,2,3],[4,5,6]]
An image              → 3D tensor:           shape [224, 224, 3]
A batch of images     → 4D tensor:           shape [32, 224, 224, 3]
</code></pre>
<p>Models accept inputs in specific shapes. If your tensor shape doesn't match the model's expected input, your code breaks. That's why understanding dimensions is practical, not just theoretical.</p>
<p>TensorFlow is literally named after this concept. Tensor + Flow = tensors flowing through neural networks.</p>
<p>Here's how you create tensors in TensorFlow.js:</p>
<pre><code class="language-javascript">// 1D tensor — a list of values
const scores = tf.tensor([0.1, 0.7, 0.2]);

// 3D tensor — a single image (height x width x RGB channels)
const image = tf.tensor([
  [[255, 0, 0], [0, 255, 0]],
  [[0, 0, 255], [255, 255, 0]]
]);

// 4D tensor — a batch of 32 images
const batch = tf.zeros([32, 224, 224, 3]);
</code></pre>
<h3 id="heading-tensorflowjs">TensorFlow.js</h3>
<p>TensorFlow.js is Google's JavaScript version of TensorFlow. It lets you run pre-trained models right in the browser and, if you really want to, train new ones completely client-side.</p>
<p>The most important concept in TensorFlow.js is the backend, the hardware your model actually runs on. You can switch between backends depending on what the user's device supports, and it makes a significant difference to performance.</p>
<pre><code class="language-javascript">await tf.setBackend('webgpu');  // fastest — true GPU compute
await tf.setBackend('webgl');   // very fast — GPU via graphics shaders
await tf.setBackend('wasm');    // fast — near-native CPU speed
await tf.setBackend('cpu');     // slowest — plain JavaScript on CPU

await tf.ready();
console.log('Running on:', tf.getBackend());
</code></pre>
<p>In practice, you want to try the fastest available backend and fall back gracefully if a user's browser doesn't support it:</p>
<pre><code class="language-javascript">const backends = ['webgpu', 'webgl', 'wasm', 'cpu'];

for (const backend of backends) {
  try {
    await tf.setBackend(backend);
    await tf.ready();
    console.log('Using backend:', backend);
    break;
  } catch {
    continue;
  }
}
</code></pre>
<h3 id="heading-webassembly">WebAssembly</h3>
<p>WebAssembly (WASM) basically lets code written in C++ or Rust run inside the browser at near-native speeds. When it comes to AI, this is a big deal because heavy math operations like tensor calculations, data preprocessing, and running compressed models happen way faster in WASM than they ever could in standard JavaScript.</p>
<p>Under the hood, TensorFlow.js's WASM backend is using a compiled C++ runtime. If you're running compressed models on a device's CPU, switching to the WASM backend can make your app anywhere from 2 to 10 times faster than just sticking with regular JavaScript.</p>
<pre><code class="language-javascript">await tf.setBackend('wasm');
await tf.ready();
</code></pre>
<h3 id="heading-webgl-and-webgpu">WebGL and WebGPU</h3>
<p>This is where browser AI performance gets interesting.</p>
<p><strong>WebGL</strong> was originally built for 3D graphics. But developers discovered that the parallel computation that GPUs use for rendering is exactly the kind of parallel computation neural networks need.</p>
<p>TensorFlow.js's WebGL backend encodes tensor operations as graphics shader programs and runs them on the GPU. It works well, but it's a workaround, as WebGL was never designed for this kind of work.</p>
<p><strong>WebGPU</strong> is what was actually designed for the job. It launched in Chrome back in April 2023 after six years of collaboration between Apple, Google, Mozilla, Intel, and Microsoft.</p>
<p>Instead of just handling graphics, it's a modern API built from the ground up for general-purpose computing. When it comes to running AI models, it can be 2 to 3 times faster than WebGL, which means you can actually run significantly larger models right in the browser.</p>
<p>Here's how to check for WebGPU support and use it:</p>
<pre><code class="language-javascript">if ('gpu' in navigator) {
  console.log('WebGPU is supported');
  await tf.setBackend('webgpu');
} else {
  console.warn('WebGPU not available, falling back to WebGL');
  await tf.setBackend('webgl');
}

await tf.ready();
</code></pre>
<p>To enable WebGPU in Chrome for development, go to:</p>
<pre><code class="language-plaintext">chrome://flags/#enable-unsafe-webgpu → Enable → Restart Chrome
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/66058baaeb0049c5f549a186/77964ba9-2db1-4011-b6fe-17b47f48688b.png" alt="Enable web-gpu in chrome" style="display:block;margin:0 auto" width="1916" height="490" loading="lazy">

<p>The performance progression across backends looks like this:</p>
<table>
<thead>
<tr>
<th>Backend</th>
<th>What's happening under the hood</th>
<th>Relative speed</th>
</tr>
</thead>
<tbody><tr>
<td>cpu</td>
<td>Plain JavaScript on CPU</td>
<td>Slow</td>
</tr>
<tr>
<td>wasm</td>
<td>Compiled C++ on CPU</td>
<td>Fast</td>
</tr>
<tr>
<td>webgl</td>
<td>GPU via graphics shaders</td>
<td>Very fast</td>
</tr>
<tr>
<td>webgpu</td>
<td>GPU via compute shaders</td>
<td>Fastest</td>
</tr>
</tbody></table>
<h3 id="heading-mediapipe">MediaPipe</h3>
<p>MediaPipe is Google's framework for real-time perception tasks like hand tracking, face mesh detection, pose estimation, and object detection. Think of it as plug-and-play AI for anything that involves a camera.</p>
<p>You don't build these models yourself – you just import them and use them. MediaPipe is what actually powers the background blur in Google Meet and the visual filters in YouTube. Under the hood, it runs on TensorFlow.js and WebAssembly to keep everything moving fast.</p>
<p>You can try all MediaPipe models interactively before writing any code at <a href="https://mediapipe-studio.webapps.google.com/home">MediaPipe Studio</a>.</p>
<h2 id="heading-how-to-build-ai-in-the-browser">How to Build AI in the Browser</h2>
<h3 id="heading-step-1-train-a-model-with-teachable-machine">Step 1: Train a Model with Teachable Machine</h3>
<p><a href="https://teachablemachine.withgoogle.com">Teachable Machine</a> is Google's no-code tool for building models. It lets you create custom images, audio, or pose classifiers right from your webcam without needing any machine learning experience. Once you're done, you can export them as TensorFlow.js models that are completely ready to drop straight into your app.</p>
<p>Here's how to get started:</p>
<ol>
<li><p>Go to <a href="https://teachablemachine.withgoogle.com">teachablemachine.withgoogle.com</a></p>
</li>
<li><p>Choose Image Project, standard image model.</p>
</li>
<li><p>Create two or more classes. "Thumbs Up" and "Thumbs Down" is a simple starting point</p>
</li>
<li><p>Record examples for each class using your webcam</p>
</li>
<li><p>Click <strong>Train Model</strong> — training happens entirely in your browser</p>
</li>
<li><p>Click <strong>Export Model</strong> and choose <strong>TensorFlow.js</strong></p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/66058baaeb0049c5f549a186/8ec77493-cf3a-4c05-add0-3140185cc5aa.png" alt="Train with teachable machine" style="display:block;margin:0 auto" width="2874" height="1506" loading="lazy">

<p>When you export, you get three files:</p>
<ul>
<li><p><code>model.json</code>: The model architecture: layers, input/output shapes, and paths to the weights</p>
</li>
<li><p><code>weights.bin</code>: The trained weights stored as binary data</p>
</li>
<li><p><code>metadata.json</code>: Class labels, input size, and inference configuration</p>
</li>
</ul>
<h4 id="heading-a-note-on-training-data-quality">A note on training data quality</h4>
<p>Teachable Machine relies on supervised learning. You give the model labeled examples, and it figures out the underlying patterns. When you're gathering your data, two things matter way more than the sheer number of pictures you take:</p>
<ul>
<li><p><strong>Balance:</strong> If one class has significantly more examples than another, the model will be biased toward it. Keep the data roughly equal across classes.</p>
<p><strong>Variety:</strong> Fifty photos from different angles, distances, and lighting conditions will easily outperform two hundred near-identical shots from the same spot. The model needs to understand the concept of a "thumbs up", not memorise one specific photo of your specific thumb.</p>
</li>
</ul>
<p>Keep in mind that the actual machine learning model is usually just a tiny fraction of your overall codebase. The vast majority of what you write is going to be standard JavaScript. At the end of the day, it's just another asset in your stack.</p>
<h3 id="heading-step-2-setting-up-and-writing-the-code">Step 2: Setting up and Writing the Code</h3>
<p>Now that you have your model files, set up your project structure like this and create an <code>index.html</code> file:</p>
<pre><code class="language-plaintext">your-project/
├── index.html
├── model.json
├── weights.bin
└── metadata.json
</code></pre>
<p>The <code>model.json</code>, <code>weights.bin</code>, and <code>metadata.json</code> files all go in the same folder as your <code>index.html</code>. The demo loads them from the same directory using <code>const URL = "./"</code>.</p>
<p>To run it locally, open the folder in VS Code or your preferred IDE and use the <strong>Live Server</strong> extension. Just right-click <code>index.html</code> and select <strong>Open with Live Server</strong>. Opening the file directly in the browser without a server will cause CORS errors when loading the model files.</p>
<h3 id="heading-step-3-load-the-model-and-run-predictions">Step 3: Load the Model and Run Predictions</h3>
<p>Paste the following in your <code>index.html</code> file. This demo loads your Teachable Machine model, starts your webcam, and runs continuous predictions in a loop:</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;

&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
    &lt;title&gt;Teachable Machine - Webcam + Backend Switch Demo&lt;/title&gt;
    &lt;style&gt;
        body {
            font-family: Arial;
            text-align: center;
            margin: 20px;
        }

        #webcam-container {
            margin-top: 20px;
        }

        #label-container {
            margin-top: 10px;
            font-size: 18px;
            font-weight: bold;
        }

        button.backend-btn {
            margin: 5px;
            padding: 8px 16px;
            font-size: 16px;
            cursor: pointer;
        }

        #status {
            margin-top: 10px;
            font-weight: bold;
            color: #0078ff;
        }

        table {
            margin: 20px auto;
            border-collapse: collapse;
            width: 80%;
            max-width: 600px;
        }

        th,
        td {
            border: 1px solid #ccc;
            padding: 10px;
        }

        th {
            background: #0078ff;
            color: white;
        }
    &lt;/style&gt;
&lt;/head&gt;

&lt;body&gt;
    &lt;h2&gt;AI in the web Demo&lt;/h2&gt;

    &lt;div&gt;
        &lt;button class="backend-btn" onclick="switchBackend('cpu')"&gt;CPU&lt;/button&gt;
        &lt;button class="backend-btn" onclick="switchBackend('webgl')"&gt;WebGL&lt;/button&gt;
        &lt;button class="backend-btn" onclick="switchBackend('webgpu')"&gt;WebGPU&lt;/button&gt;
    &lt;/div&gt;

    &lt;p id="status"&gt;Click a backend to start&lt;/p&gt;

    &lt;table&gt;
        &lt;thead&gt;
            &lt;tr&gt;
                &lt;th&gt;Backend&lt;/th&gt;
                &lt;th&gt;Load Time (s)&lt;/th&gt;
                &lt;th&gt;Inference Time (ms)&lt;/th&gt;
                &lt;th&gt;Status&lt;/th&gt;
            &lt;/tr&gt;
        &lt;/thead&gt;
        &lt;tbody id="results"&gt;&lt;/tbody&gt;
    &lt;/table&gt;

    &lt;div id="webcam-container"&gt;&lt;/div&gt;
    &lt;div id="label-container"&gt;&lt;/div&gt;

    &lt;script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@latest/dist/tf.min.js"&gt;&lt;/script&gt;
    &lt;script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-webgpu"&gt;&lt;/script&gt;
    &lt;script
        src="https://cdn.jsdelivr.net/npm/@teachablemachine/image@latest/dist/teachablemachine-image.min.js"&gt;&lt;/script&gt;

    &lt;script&gt;
        const URL = "./";
        const resultsTable = document.getElementById("results");
        const statusEl = document.getElementById("status");
        const backends = ["cpu", "webgl", "webgpu"];

        let model, webcam, maxPredictions;
        const backendResults = {};

        // Initialize webcam
        async function initWebcam() {
            if (!webcam) {
                webcam = new tmImage.Webcam(200, 200, true);
                await webcam.setup();
                await webcam.play();
                document.getElementById("webcam-container").appendChild(webcam.canvas);

                const labelContainer = document.getElementById("label-container");
                labelContainer.innerHTML = "";
                for (let i = 0; i &lt; 2; i++) labelContainer.appendChild(document.createElement("div"));
            }
        }

        async function switchBackend(backend) {
            statusEl.innerText = `Switching to ${backend.toUpperCase()}...`;

            await initWebcam();

            try {
                const startLoad = performance.now();
                await tf.setBackend(backend);
                await tf.ready();
                model = await tmImage.load(URL + "model.json", URL + "metadata.json");
                maxPredictions = model.getTotalClasses();
                const endLoad = performance.now();
                const loadTime = ((endLoad - startLoad) / 1000).toFixed(2);

                // Single inference to measure time
                const startInference = performance.now();
                await model.predict(webcam.canvas);
                const endInference = performance.now();
                const inferenceTime = (endInference - startInference).toFixed(1);

                // Store results
                backendResults[backend] = { loadTime, inferenceTime };

                updateTable();

                statusEl.innerText = `${backend.toUpperCase()} ready`;
            } catch (err) {
                console.error(`${backend} not supported:`, err);
                statusEl.innerText = `${backend.toUpperCase()} not supported`;
            }
        }


        function updateTable() {
            resultsTable.innerHTML = "";
            for (let backend of backends) {
                const row = document.createElement("tr");
                const backendCell = document.createElement("td");
                const loadCell = document.createElement("td");
                const inferenceCell = document.createElement("td");
                const statusCell = document.createElement("td");

                backendCell.textContent = backend.toUpperCase();

                if (backendResults[backend]) {
                    loadCell.textContent = backendResults[backend].loadTime;
                    inferenceCell.textContent = backendResults[backend].inferenceTime;
                    statusCell.textContent = "✓";
                } else {
                    loadCell.textContent = "-";
                    inferenceCell.textContent = "-";
                    statusCell.textContent = "-";
                }

                row.appendChild(backendCell);
                row.appendChild(loadCell);
                row.appendChild(inferenceCell);
                row.appendChild(statusCell);
                resultsTable.appendChild(row);
            }
        }

        // Continuous prediction loop
        async function loop() {
            if (webcam &amp;&amp; model) {
                webcam.update();
                const prediction = await model.predict(webcam.canvas);
                const labelContainer = document.getElementById("label-container");
                labelContainer.innerHTML = "";
                for (let i = 0; i &lt; maxPredictions; i++) {
                    const p = document.createElement("div");
                    p.textContent = `\({prediction[i].className}: \){(prediction[i].probability * 100).toFixed(1)}%`;
                    labelContainer.appendChild(p);
                }
            }
            requestAnimationFrame(loop);
        }

        loop();
    &lt;/script&gt;
&lt;/body&gt;

&lt;/html&gt;
</code></pre>
<p>A few things worth understanding about what this code is doing:</p>
<p>The <code>switchBackend</code> function does more than just swap the backend. Each time you click a backend button, it records how long the model takes to load on that backend and how long a single inference takes. Those numbers go straight into the comparison table so you can see the difference without having to look at console logs.</p>
<p>The <code>loop</code> function runs continuously using <code>requestAnimationFrame</code>. Every frame, it grabs the current webcam image, passes it to the model, and updates the prediction labels on screen. This is what makes the detection feel real-time.</p>
<p>Notice that <code>initWebcam</code> only runs once. It checks if <code>webcam</code> already exists before setting up. Switching backends reloads the model but keeps the same webcam stream running.</p>
<p>Open Chrome DevTools and go to the <strong>Network tab</strong> while the demo runs. After the model files finish loading, you'll see zero outbound requests. Every prediction is happening entirely in the browser.</p>
<h3 id="heading-step-4-switch-backends-and-compare-performance">Step 4: Switch Backends and Compare Performance</h3>
<p>Once the demo is running, click each backend button one at a time: CPU, then WebGL, then WebGPU. The table updates after each switch and shows you the load time in seconds and inference time in milliseconds for each backend side by side.</p>
<p>Here's what you should expect to see:</p>
<ul>
<li><p><strong>CPU</strong> will be the slowest with everything running in plain JavaScript</p>
</li>
<li><p><strong>WebGL</strong> will be noticeably faster as the GPU is now handling the tensor operations</p>
</li>
<li><p><strong>WebGPU</strong> will be the fastest with true GPU compute and less overhead than WebGL. The exact numbers depend on your machine, but the gap between CPU and WebGPU is usually significant enough to see immediately in the table.</p>
</li>
</ul>
<img src="https://cdn.hashnode.com/uploads/covers/66058baaeb0049c5f549a186/6332c651-9b96-45c7-95cc-0d842595ff51.png" alt="Demo with network tab" style="display:block;margin:0 auto" width="2864" height="816" loading="lazy">

<p><strong>Note:</strong> WebGPU requires Chrome with the flag enabled. If the WebGPU button shows "not supported", go to <code>chrome://flags/#enable-unsafe-webgpu</code>, enable it, and restart Chrome.</p>
<h2 id="heading-chromes-built-in-ai-apis">Chrome's Built-in AI APIs</h2>
<p>Beyond loading your own models, Chrome is rolling out native AI capabilities that you can hook into directly through browser APIs. This means no managing bulky model files, no importing TensorFlow.js, and zero manual setup.</p>
<p>The powerhouse here is Gemini Nano, a lightweight version of Google's Gemini model built to run completely on-device inside Chrome. It handles tasks like smart replies and page summarization right in the browser without ever making a cloud call.</p>
<p>If you want to build with it, you can tap into these experimental APIs that Chrome exposes to developers:</p>
<pre><code class="language-plaintext">chrome://flags → search "Prompt API for Gemini Nano" → Enable → Restart Chrome
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/66058baaeb0049c5f549a186/c1db08aa-b5b1-4496-9553-536bbc68a442.png" alt="Gemini nano" style="display:block;margin:0 auto" width="2880" height="1800" loading="lazy">

<p>These are still experimental and behind flags. But they show clearly where the platform is heading.</p>
<p>For the full prerequisites and setup guide for Chrome's built-in AI, see the <a href="https://developer.chrome.com/docs/ai/get-started">official Chrome AI getting started documentation</a>.</p>
<h2 id="heading-where-web-ai-is-headed">Where Web AI Is Headed</h2>
<p>The browser is evolving into something that doesn't really have a clean name yet. It's no longer just a document viewer, and it's not quite a native app runtime either. Instead, it's becoming an intelligent edge node – a piece of infrastructure that can perceive, process, and act all on its own, without constantly phoning home for permission.</p>
<p>A few massive shifts are already well underway:</p>
<ul>
<li><p><strong>Native AI built directly into the platform:</strong> AI capabilities are turning into standard browser APIs. Because they're cached and shared across the entire ecosystem, you won't have to re-download massive models for every single domain you visit.  </p>
<p>Browsers designed with AI as their core foundation are already popping up. OpenAI's Atlas browser is a perfect early signal of this trend. Every year, the idea of the browser acting as an intelligent agent platform rather than a simple content renderer gets more concrete.</p>
</li>
<li><p><strong>The developer shift:</strong> For developers, the immediate future is clear: a significant chunk of AI features that currently live on expensive servers will migrate straight to the client side. It won't be everything, but the lightweight, high-frequency, and privacy-sensitive tasks will absolutely make the jump.</p>
</li>
</ul>
<p>WebGPU isn't just a flashy demo technology, and browser inference is definitely not a toy. These are serious production tools, and they're only getting more capable as AI models shrink and user hardware gets more powerful.</p>
<p>If you're currently building an interactive, AI-powered feature, it's well worth pausing to ask yourself: <em>does this actually need a server?</em></p>
<p>Sometimes the answer is still yes. But more and more often, the answer is a definitive no.</p>
<h2 id="heading-what-you-learned">What You Learned</h2>
<p>In this tutorial, we covered:</p>
<ul>
<li><p>What Web AI is and how it differs from cloud-based AI</p>
</li>
<li><p>When to use browser AI versus cloud AI and how a hybrid approach works</p>
</li>
<li><p>The technology stack behind browser AI: tensors, TensorFlow.js, WebAssembly, WebGL, WebGPU, and MediaPipe</p>
</li>
<li><p>How to train a custom model with Teachable Machine and export it for the browser</p>
</li>
<li><p>How to load that model, run it against live webcam input, and manage GPU memory correctly</p>
</li>
<li><p>How to benchmark WebGL vs WebGPU inference times to measure real performance differences</p>
</li>
<li><p>How to access Chrome's built-in AI APIs including Gemini Nano</p>
</li>
</ul>
<p>If you found this useful or want to connect, you can find me on <a href="https://twitter.com/timi471">Twitter/X</a> or <a href="https://www.linkedin.com/in/ayantunji-timilehin">LinkedIn</a>.</p>
<h2 id="heading-resources">Resources</h2>
<ul>
<li><p><a href="https://www.tensorflow.org/js">TensorFlow.js Documentation</a></p>
</li>
<li><p><a href="https://teachablemachine.withgoogle.com">Teachable Machine</a></p>
</li>
<li><p><a href="https://mediapipe-studio.webapps.google.com/home">MediaPipe Studio</a></p>
</li>
<li><p><a href="https://developer.chrome.com/docs/web-platform/webgpu">WebGPU in Chrome</a></p>
</li>
<li><p><a href="https://developer.chrome.com/docs/ai/get-started">Chrome Built-in AI — Getting Started</a></p>
</li>
<li><p><a href="https://developer.chrome.com/docs/ai/translator-api">Chrome AI Translator API</a></p>
</li>
<li><p><a href="https://github.com/GoogleChromeLabs/web-ai-demos">Google Web AI Demos on GitHub</a></p>
</li>
<li><p><a href="https://huggingface.co/docs/transformers.js">Hugging Face Transformers.js</a></p>
</li>
<li><p><a href="https://webllm.mlc.ai">WebLLM — Run LLMs in the Browser</a></p>
</li>
<li><p><a href="https://blog.google/products/chrome/new-ai-features-for-chrome/">Chrome AI Features — Google Blog</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Optimize Next.js Web Apps for Better Performance ]]>
                </title>
                <description>
                    <![CDATA[ As engineers, we often get so carried away with other aspects of development that we overlook how users perceive and interact with our applications. This oversight can result in users leaving the app almost as soon as they arrive, leading to higher b... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/optimize-nextjs-web-apps-for-better-performance/</link>
                <guid isPermaLink="false">6776a323218b455d646035b2</guid>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ web performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ web ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Performance Optimization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ optimization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayantunji Timilehin ]]>
                </dc:creator>
                <pubDate>Thu, 02 Jan 2025 14:30:59 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1735828217839/b65374be-d891-4f19-a359-f84f2ac8f3b9.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As engineers, we often get so carried away with other aspects of development that we overlook how users perceive and interact with our applications. This oversight can result in users leaving the app almost as soon as they arrive, leading to higher bounce rates and minimal engagement.</p>
<p>At its core, every business thrives on delivering value to its users. When users are unable to access this value due to poor performance, it ultimately impacts the business's success. Slow load times, among other factors, frustrate users and drive them away before they even get a chance to engage.</p>
<p>Optimizing performance is more than just a technical detail – it’s also a critical part of creating a successful application. Without it, even the best features can go unnoticed if users don’t stick around long enough to see them.</p>
<p>In this article, we’ll explore key approaches to optimize your Next.js application, making it faster and more efficient.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-building-a-performant-app">Building a Performant Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-optimize-your-applications">How to Optimize Your Applications</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-key-techniques-to-optimize-performance">Key Techniques To Optimize Performance</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-using-the-nextjs-image-component">Using The Next.js Image Component</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-optimizing-third-party-scripts-with-the-nextjs-script-component">Optimizing Third-Party Scripts with the Next.js Script Component</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-remove-unused-packagesdependencies">Remove Unused Packages/Dependencies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-4-caching-and-incremental-static-regeneration-isr">Caching and Incremental Static Regeneration (ISR)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-caching-frequently-used-content">Caching Frequently Used Content</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-5-font-optimization-with-nextfont">Font Optimization With next/font</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-6-lazy-loading-and-code-splitting">Lazy Loading And Code Splitting</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lazy-loading-in-nextjs">Lazy Loading in Next.js</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-code-splitting">Code Splitting</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-building-a-performant-application">Building a Performant Application</h2>
<p>Making your apps more performant means striking the right balance between speed, responsiveness, and efficient use of resources. You should strive to create an application that delivers value and keeps users satisfied.</p>
<p>Building a performant app is about making sure the app feels smooth and intuitive so that there are no frustrating lags when a user clicks buttons, scrolls, or navigates around. You’ll also want to make sure that data loads or updates without unnecessary delays.</p>
<h2 id="heading-how-to-optimize-your-applications">How to Optimize Your Applications</h2>
<p>The first step in optimizing your application is identifying problem areas. A number of tools and packages can help you analyze your application's performance effectively. Here's how you can use them:</p>
<h3 id="heading-using-npm-run-build">Using <code>npm run build</code></h3>
<p>When you run <code>npm run build</code>, Next.js creates a production-ready version of your application and gives a detailed breakdown of your pages. This includes:</p>
<ul>
<li><p><strong>Size</strong>: The size of the JavaScript files for each route. Highlighting any routes that are too large and could slow things down. Smaller page sizes generally result in faster load times while large pages might take longer to download, especially for users with slower network connections.</p>
</li>
<li><p><strong>First Load Js</strong>: This column provides information about the total amount of JavaScript the browser needs to download and execute to fully render the page for the first time. Large <strong>First Load JS</strong> values</p>
<p>  cause Slower Time-to-Interactive (TTI).</p>
</li>
</ul>
<p>Running this command produces an analysis like below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734639730677/cfd1f858-a9df-4e6c-af28-454857309156.png" alt="Example result of running npm run build" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-using-nextbundle-analyzer">Using <code>@next/bundle-analyzer</code></h3>
<p>The <a target="_blank" href="https://www.npmjs.com/package/@next/bundle-analyzer">bundle analyzer</a> is a package provided by Next.js to analyze the size of JavaScript bundles by providing a visual representation of the application’s module and dependencies. Here’s how to use the package:</p>
<p>First, install the package by running this command:</p>
<pre><code class="lang-bash">npm install @next/bundle-analyzer
</code></pre>
<p>Or you can use yarn:</p>
<pre><code class="lang-bash">yarn add @next/bundle-analyzer
</code></pre>
<p>Then add the <code>@next/bundle-analyzer</code> configuration to your <code>next.config.js</code> file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> withBundleAnalyzer = <span class="hljs-built_in">require</span>(<span class="hljs-string">'@next/bundle-analyzer'</span>)({
  <span class="hljs-attr">enabled</span>: process.env.ANALYZE === <span class="hljs-string">'true'</span>,
});

<span class="hljs-built_in">module</span>.exports = withBundleAnalyzer({
  <span class="hljs-comment">// other Next.js config options here</span>
});
</code></pre>
<p>To analyze your application bundles while generating a production build, run the following command:</p>
<pre><code class="lang-bash">ANALYZE=<span class="hljs-literal">true</span> npm run build
</code></pre>
<p>For a step-by-step guide on how to use the bundle analyzer effectively, check out this detailed <a target="_blank" href="https://www.youtube.com/watch?v=EIGmcxwbbZw">video tutorial</a></p>
<h3 id="heading-browser-tools">Browser tools</h3>
<p>Finally, modern browsers, including Google Chrome, Firefox, and Edge, offer powerful tools to analyze and improve your application's performance. Features like the Performance Tab help you record and visualize how your application runs, pinpointing issues like slow rendering or long tasks.</p>
<p>You can also use tools like Lighthouse (available in Chrome and Edge) to generate automated audits, highlighting problems such as large assets and unoptimized resources.</p>
<p>To access the <strong>Lighthouse</strong> and <strong>Performance</strong> tabs:</p>
<ol>
<li><p>Open your browser's developer tools by right-clicking anywhere on the browser and selecting the <strong>Inspect</strong> option or pressing <strong>Command + Option + I</strong> (on Mac) or <strong>Ctrl + Shift + I</strong> (on Windows).</p>
</li>
<li><p>Look at the top menu in the developer tools.</p>
</li>
<li><p>If you don’t see the <strong>Lighthouse</strong> or <strong>Performance</strong> tabs right away, click the <strong>double right arrow (&gt;&gt;)</strong> to reveal hidden tabs.</p>
</li>
<li><p>Select the desired tab to start analyzing performance or generating a Lighthouse report.</p>
</li>
</ol>
<p>Here is an example of a generated audit in the Performance tab on Chrome</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735616911745/e5f09934-df99-40fc-b194-a292a21a4517.png" alt="image of the performance tab on chrome browser" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Here’s another image showing the generated audit by lighthouse</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1735617075187/dfde608b-eeb7-443d-81c6-56ff2a6dd92b.png" alt="dfde608b-eeb7-443d-81c6-56ff2a6dd92b" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-key-techniques-to-optimize-performance">Key Techniques to Optimize Performance</h2>
<h3 id="heading-1-using-the-nextjs-image-component">1.) Using The Next.js <code>Image</code> Component</h3>
<p>Images often account for the largest portion of page weight, directly affecting load times and user experience. Large images slow down rendering and ultimately, increase bandwidth usage.</p>
<p>Next.js has a built-in <code>Image</code> component that automatically optimizes images, making it very useful for web performance. It takes care of resizing, lazy loading, and format optimization, so images are served in the most performant format (like .WebP) when the browser supports it.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Image <span class="hljs-keyword">from</span> <span class="hljs-string">'next/image'</span>;

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Image</span>
      <span class="hljs-attr">src</span>=<span class="hljs-string">"/house.jpg"</span>
      <span class="hljs-attr">alt</span>=<span class="hljs-string">"House Image"</span>
      <span class="hljs-attr">width</span>=<span class="hljs-string">{700}</span>
      <span class="hljs-attr">height</span>=<span class="hljs-string">{500}</span>
      <span class="hljs-attr">priority</span>=<span class="hljs-string">{false}</span> // <span class="hljs-attr">Lazy</span> <span class="hljs-attr">loads</span> <span class="hljs-attr">the</span> <span class="hljs-attr">image</span> <span class="hljs-attr">by</span> <span class="hljs-attr">default</span>
    /&gt;</span></span>
</code></pre>
<p>In the snippet above,</p>
<ul>
<li><p><code>src="/house.jpg"</code>: This points to the image file's location, which is in the <code>public</code> folder. Images in the <code>/public</code> directory are served statically, so you don’t need extra configuration.</p>
</li>
<li><p><code>alt="House Image"</code>: The <code>alt</code> text (just like in the native HTML <code>image</code> element) provides a description of the image, which is great for accessibility (like screen readers) and also helps with SEO.</p>
</li>
<li><p><code>width &amp; heigh</code>t: By explicitly setting the width and height, Next.js can calculate the space the image will occupy on the page before it loads. This prevents the page layout from shifting as the image loads, which improves user experience and boosts performance metrics like <a target="_blank" href="https://blog.hubspot.com/marketing/cumulative-layout-shift">Cumulative Layout Shift</a> (as shown in the image above).</p>
</li>
<li><p><code>priority={false}</code>: This ensures the image will only load when it's near the user's viewport conserving the bandwidth and improving page load times for non-critical images. However, for important images that should load immediately (like those visible as soon as the page opens), you can set <code>priority={true}</code> to bypass lazy loading and ensure the image loads as quickly as possible.</p>
</li>
</ul>
<p>One of the key advantages of the Next.js <code>Image</code> component is its built-in <strong>lazy loading</strong> feature. This means that images won’t be loaded until they are actually needed (when they enter the viewport). By only loading images that are about to be viewed, performance is improved and pages can load faster, even with many high-quality images.</p>
<h3 id="heading-2-optimizing-third-party-scripts-with-the-nextjs-script-component">2.) Optimizing Third-Party Scripts with the Next.js Script Component</h3>
<p>Third-party scripts, such as analytics tools or advertising networks, can heavily affect your application's performance if not properly managed. Next.js has a <strong>Script</strong> component that makes it easy to load scripts efficiently, giving you control over how and when they load.</p>
<p>The <code>Script</code> component allows you to define a <strong>loading strategy</strong> for scripts, determining when and how they are fetched and executed. By prioritizing or deferring scripts based on their importance, you can improve the overall performance and user experience of your application.</p>
<ul>
<li><p><code>beforeInteractive</code><strong>:</strong> Use this strategy for scripts that must load before the page becomes interactive, like essential analytics or monitoring tools.</p>
</li>
<li><p><code>afterInteractive</code>: When you use this strategy, the script loads after the page becomes interactive, which is the default behavior. This is ideal for scripts that add functionality but aren’t essential for initial rendering.</p>
</li>
<li><p><code>lazyOnload</code>: Defers loading the script until all other page resources have finished loading. This is perfect for non-essential scripts like ads or social media widgets.</p>
</li>
</ul>
<pre><code class="lang-javascript">&lt;Script src=<span class="hljs-string">"https://example.com/non-essential.js"</span> strategy=<span class="hljs-string">"lazyOnload"</span> /&gt; <span class="hljs-comment">//Pass the strategy as a prop to the component</span>
</code></pre>
<p>By leveraging the Next.js <code>Script</code> component, you can prevent scripts from blocking critical rendering, reducing load times and improve <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Glossary/Time_to_interactive">Time to Interactive</a> (TTI).</p>
<h3 id="heading-3-remove-unused-packagesdependencies">3.) Remove Unused Packages/Dependencies</h3>
<p>Over time, as you build and maintain your project, unused dependencies can pile up in your codebase. These unnecessary packages increase the size of your project, slow down installation times, and make the code harder to maintain. Cleaning up these unused dependencies is essential for optimizing your application's performance and keeping your codebase clean.</p>
<p>The <a target="_blank" href="https://www.npmjs.com/package/depcheck">depcheck</a> tool is a great way to identify and remove unused dependencies from your project. It analyzes your <code>package.json</code> and the project files to find unused dependencies, unused devDependencies, and missing dependencies.</p>
<p>You can run a <code>depcheck</code> like this:</p>
<pre><code class="lang-bash">npx depcheck
</code></pre>
<p>After identifying the unused dependencies, you can remove them by running:</p>
<pre><code class="lang-bash">npm uninstall &lt;package-name&gt;
</code></pre>
<p>or with yarn:</p>
<pre><code class="lang-bash">yarn remove &lt;package-name&gt;
</code></pre>
<p>Regularly running <code>depcheck</code> is a simple yet effective way to keep your project clean and efficient.</p>
<h3 id="heading-4-caching-and-incremental-static-regeneration-isr">4.) Caching and Incremental Static Regeneration (ISR)</h3>
<p>When you find yourself running the same calculations or database queries repeatedly, you should consider caching. It’s a simple yet powerful way to boost your web application's performance, especially for content that doesn’t change often. By storing frequently accessed data in a cache, you can avoid unnecessary processing and speed up load times.</p>
<p>In Next.js, you can take this a step further with Incremental Static Regeneration (ISR), which lets you serve static content instantly while keeping it fresh behind the scenes.</p>
<p><strong>Incremental Static Regeneration (ISR)</strong> in Next.js lets you update static pages without rebuilding the whole site. Here's how it works:</p>
<ol>
<li><p><strong>Build time generation</strong>: ISR generates pages when the site is built.</p>
</li>
<li><p><strong>Caching</strong>: It stores the pages so they load quickly when users visit.</p>
</li>
<li><p><strong>Background updates</strong>: When content changes, ISR updates the pages behind the scenes without affecting users.</p>
</li>
<li><p><strong>Dynamic updates</strong>: It combines the fast loading of static pages with the ability to update content regularly.</p>
</li>
</ol>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> fetchData();

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: { data },
    <span class="hljs-comment">//regenerate the page every 20 seconds.</span>
    <span class="hljs-attr">revalidate</span>: <span class="hljs-number">20</span>,
  };
}

<span class="hljs-comment">//pre-render the page as static content</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyPage</span>(<span class="hljs-params">{ data }</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>My Page<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{data}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyPage;
</code></pre>
<h3 id="heading-caching-frequently-used-content">Caching Frequently Used Content</h3>
<p>For websites with pages that get a lot of visitors, like product listings or blog posts, it's important to keep the content fast and up-to-date.</p>
<p>Caching helps achieve this by saving a copy of the page so it doesn't need to be created from scratch each time someone visits. The browser or server will store this cached page for a set amount of time, which is controlled by caching headers. Meanwhile, ISR (Incremental Static Regeneration) ensures that the page can be updated in the background when necessary, without needing to rebuild the entire site.</p>
<p>In applications with lots of data, caching can also speed up the process by storing API responses. This way, when users request the same data again, they can get it quickly from the cache instead of waiting for it to be fetched anew. Tools like Vercel and Content Delivery Networks (CDNs) help by storing these cached pages in multiple locations around the world, so visitors can access them faster.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> fetchData();

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">props</span>: { data },
    <span class="hljs-comment">// Regenerate page at most once every 30 seconds</span>
    <span class="hljs-attr">revalidate</span>: <span class="hljs-number">30</span>,
    <span class="hljs-comment">// Cache for 1 hour at the CDN level</span>
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">'Cache-Control'</span>: <span class="hljs-string">'public, max-age=3600, must-revalidate'</span>,
    },
  };
}
</code></pre>
<p>Here, the page regenerates every 30 seconds and is cached at the CDN level for one hour. The <code>Cache-Control</code> header tells the CDN and browser to cache the page for 1 hour and revalidate it afterward.</p>
<p>For a deeper dive into caching and its role in web performance, check out this insightful <a target="_blank" href="https://www.freecodecamp.org/news/caching-vs-content-delivery-network/">freeCodeCamp article on Caching vs. Content Delivery Networks</a>.</p>
<h3 id="heading-5-font-optimization-with-nextfont">5.) Font Optimization With <code>next/font</code></h3>
<p>The <code>next/font</code> module in Next.js automatically handles font loading for improved performance, so you don’t need to manually configure or use extra libraries. It loads only the essential parts of the font, which results in faster page load times.</p>
<p>To further reduce the font file size, you can provide the <code>subsets</code> array which ensures fewer bytes are transferred and pages load quickly.</p>
<p>Here’s how it works:</p>
<ul>
<li><p><strong>Automatic font loading</strong>: The module optimizes font loading automatically, making sure fonts are served in the most efficient way, improving performance without extra effort.</p>
</li>
<li><p><strong>Subsetting fonts</strong>: You can specify the exact font characters needed for your app.</p>
</li>
<li><p><strong>Font display strategy</strong>: The font-display strategy determines how text is shown to the user while fonts are loading. Next.js typically uses the <code>swap</code> strategy by default, but you can manually configure it if necessary. The most common strategies are <code>swap</code> <code>fallback</code> <code>optional</code> and <code>block</code>.</p>
</li>
<li><pre><code class="lang-javascript">  <span class="hljs-keyword">import</span> { Inter } <span class="hljs-keyword">from</span> <span class="hljs-string">'next/font/google'</span>

  <span class="hljs-keyword">const</span> inter = Inter({
    <span class="hljs-attr">subsets</span>: [<span class="hljs-string">'latin'</span>, <span class="hljs-string">'latin-ext'</span>], <span class="hljs-comment">// Load only the Latin and extended Latin subsets</span>
    <span class="hljs-attr">weight</span>: <span class="hljs-string">'400'</span>, <span class="hljs-comment">// Choose the specific weight you need</span>
    <span class="hljs-attr">style</span>: <span class="hljs-string">'normal'</span>, <span class="hljs-comment">// Specify the style if needed</span>
  })

  <span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Page</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{inter.className}</span>&gt;</span>Hello World<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  }
</code></pre>
</li>
</ul>
<p>The snippet above uses the Next.js built-in tool for Google Fonts. Instead of adding the font link in your HTML or using a third-party library, you can import it directly like this for ease and efficiency.s</p>
<ul>
<li><p><strong>subsets:</strong> Tells the app to load only the characters needed. Skipping other character sets like Cyrillic (used in Russian) or Greek, avoids downloading extra, unnecessary data, which keeps your app lightweight and faster to load.</p>
</li>
<li><p><strong>weight:</strong> Instead of loading all font weights (e.g., Bold, Light), you only bring in Regular (400). This reduces the overall size.</p>
</li>
<li><p><strong>style:</strong> Stick with the standard style (no fancy italics). This also trims down what’s downloaded.</p>
</li>
</ul>
<h3 id="heading-6-lazy-loading-and-code-splitting">6.) Lazy Loading and Code Splitting</h3>
<p>When building web apps, you want to make sure your users don’t wait too long for your pages to load. A big part of this involves reducing how much JavaScript is loaded when the page first opens. Two techniques that help with this are <strong>lazy loading</strong> and <strong>code splitting</strong>, both of which Next.js makes easy to use.</p>
<h4 id="heading-lazy-loading-in-nextjs">Lazy Loading in Next.js</h4>
<p>Think of lazy loading like waiting to download a movie only when you decide to watch it. Imagine you have a large component like a chart or a map that users only see after interacting with a page. Instead of loading it upfront, you can tell Next.js to load it only when it’s needed using <code>next/dynamic</code>.</p>
<h4 id="heading-code-splitting-in-nextjs">Code Splitting in Next.js</h4>
<p>Code splitting breaks your JavaScript into smaller pieces (called bundles), so users only load what’s necessary. For example, if a user visits your homepage, there’s no need to load JavaScript for other pages like "About Us" or "Dashboard". It typically happens during the build process or dynamically at runtime.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> dynamic <span class="hljs-keyword">from</span> <span class="hljs-string">'next/dynamic'</span>

<span class="hljs-comment">// Load HeavyComponent only when it’s rendered</span>
<span class="hljs-keyword">const</span> HeavyComponent = dynamic(<span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">'./HeavyComponent'</span>), { <span class="hljs-attr">ssr</span>: <span class="hljs-literal">false</span> })

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Welcome Home!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">HeavyComponent</span> /&gt;</span> {/* This loads only when rendered */}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  )
}
</code></pre>
<p>In the above code, <code>dynamic</code> dynamically imports the component only when needed. <code>ssr: false</code> disables server-side rendering for the component, which can save resources if the component doesn’t need to be pre-rendered.</p>
<p>Next.js automatically splits code by page, meaning each page only loads the necessary JavaScript when accessed, improving load times. For more granular control, <code>next/dynamic</code> allows you to dynamically import specific components, ensuring they are loaded lazily only when needed. While Next.js handles page-level code splitting by default, using <code>next/dynamic</code> gives you the flexibility to apply component-level splitting, optimizing resource loading and enhancing performance.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Creating a high-performance application is a very important aspect of any business. A faster and more efficient application enhances user engagement, lowers bounce rates, and boosts SEO rankings, which all contribute to business growth and customer satisfaction.</p>
<p>By utilizing these techniques we discussed in this guide, you can provide a smooth user experience while maintaining optimal efficiency behind the scenes.</p>
<p>Remember, every second saved in load time translates to happier users and, ultimately, better business outcomes.</p>
<p>Thank you for reading!</p>
<p>Want to connect with me?</p>
<ul>
<li><p>Twitter / X: <a target="_blank" href="https://x.com/Timi471">@timi471</a></p>
</li>
<li><p>Linkedin: <a target="_blank" href="https://www.linkedin.com/in/timilehin-micheal/">Ayantunji Timilehin</a></p>
</li>
<li><p>Email: ayantunjitimilehin@gmail.com</p>
</li>
</ul>
<h3 id="heading-references">References</h3>
<ul>
<li><p><a target="_blank" href="https://nextjs.org/docs/pages/building-your-application/optimizing">Next.Js Documentation</a></p>
</li>
<li><p><a target="_blank" href="https://www.freecodecamp.org/news/caching-vs-content-delivery-network/">Caching-vs-content-delivery-network</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=EIGmcxwbbZw">Using next/bundle-analyzer</a></p>
</li>
<li><p><a target="_blank" href="https://blog.hubspot.com/marketing/cumulative-layout-shift">Cumulative layout shift</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Multilingual Apps with i18n in React ]]>
                </title>
                <description>
                    <![CDATA[ I recently worked on an exciting project that involved creating a website capable of switching between languages to appeal to a broader audience. This made me understand the concept of "localization" better, which typically entails adapting content t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-multilingual-apps-with-i18n-in-react/</link>
                <guid isPermaLink="false">6750aec5bb4ed9c12f206d7d</guid>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Accessibility ]]>
                    </category>
                
                    <category>
                        <![CDATA[ localization ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayantunji Timilehin ]]>
                </dc:creator>
                <pubDate>Wed, 04 Dec 2024 19:34:29 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1733137904769/d29dd9ea-5794-4fbe-ac1c-066f6a216cb2.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>I recently worked on an exciting project that involved creating a website capable of switching between languages to appeal to a broader audience. This made me understand the concept of "localization" better, which typically entails adapting content to make it relevant, accessible, and relatable for users in different languages and regions.</p>
<p>Localization isn’t just about translating words, it’s about creating an experience that makes users feel at home, no matter their language. For example, global platforms like Amazon make language switching so seamless that it feels almost magical. Beyond enhancing user experience, this feature plays a crucial role in boosting businesses by reaching a wider audience and fostering stronger connections with customers worldwide.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-table-of-contents">Table of Contents</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-i18n-and-why-use-it">What is i18n, and Why Use It?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-lets-dive-right-in">Let’s dive right in</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-how-to-set-up-the-project">Step 1: How to Set Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-how-to-set-up-internationalization-with-i18next">Step 2: How to Set Up Internationalization with i18next</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-how-to-build-components">Step 3: How to Build Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-main-app-component">Step 4: Main App Component</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-references">References</a></p>
</li>
</ul>
<h2 id="heading-what-is-i18n-and-why-use-it">What is i18n, and Why Use It?</h2>
<p>i18n, short for internationalization, means that an application supports multiple languages. "i18n" is derived from the fact that there are 18 letters between the first "i" and the last "n" in "internationalization." It’s all about making your app adaptable for global audiences by handling text translation, formatting dates and numbers, managing currencies, and accommodating regional conventions.</p>
<p>By enabling internationalization, your app becomes not just a tool but an inclusive platform that speaks directly to a user’s preference and culture.</p>
<h2 id="heading-lets-dive-right-in">Let’s dive right in</h2>
<p>We’ll create a very simple demo multilingual web application with a dark mode toggle feature to demonstrate how to achieve this concept.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<ol>
<li><p>Basic Knowledge of React - You should understand how to create components, manage state, and use Hooks like <code>useState</code> and <code>useEffect</code>. If you’re new to React, I recommend starting with <a target="_blank" href="https://react.dev/">the official React docs</a> <a target="_blank" href="https://react.dev/">for a solid foundation</a>.</p>
</li>
<li><p>Familiarity with Internationalization Concepts - Knowing the basics of internationalization (i18n) and why it’s important will give you context for the project. This article's earlier sections cover the essentials.</p>
</li>
<li><p>Tailwind CSS - We'll use Tailwind CSS for styling. It's a utility-first CSS framework that helps you build modern, responsive designs without leaving your HTML. If you’re unfamiliar, check out <a target="_blank" href="https://tailwindcss.com/docs/installation">Tailwind's documentatio</a><a target="_blank" href="https://tailwindcss.com/docs/installation">n.</a></p>
</li>
<li><p>Node.js - Make sure Node.js is installed on your system to handle dependencies. You can download the latest version from <a target="_blank" href="https://nodejs.org/">Node.js</a>.</p>
</li>
<li><p>Package Manager - Either npm (included with Node.js) or yarn is needed to manage project dependencies</p>
</li>
</ol>
<h3 id="heading-tools-well-be-using">Tools we’ll be using</h3>
<ol>
<li><p>Code Editor</p>
</li>
<li><p>Localization Library: <a target="_blank" href="https://www.i18next.com/">react-i18next</a></p>
</li>
<li><p>Icons Library: <a target="_blank" href="https://www.npmjs.com/package/heroicons">hero-icons</a></p>
</li>
</ol>
<h2 id="heading-step-1-how-to-set-up-the-project">Step 1: How to Set Up the Project</h2>
<h3 id="heading-initialize-the-project">Initialize the Project</h3>
<p>Use Vite for fast setup:</p>
<pre><code class="lang-bash">npm create vite@latest multilingual-demo
</code></pre>
<p>Follow the instructions that show up in your terminal, selecting React and TypeScript for development as shown in the image below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733093238523/2d3ee169-bc99-4498-9779-b07067d5e5ee.png" alt="Image of React and Typescript installation" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h3 id="heading-install-dependencies">Install Dependencies</h3>
<p>Run the following commands in your terminal to install the dependencies required for this project:</p>
<pre><code class="lang-bash">npm install i18next react-i18next i18next-browser-languagedetector i18next-http-backend heroicons 
npm install tailwindcss postcss autoprefixer  
npx tailwindcss init
</code></pre>
<h3 id="heading-configure-tailwindcss"><strong>Configure TailwindCSS</strong></h3>
<p>Update the <code>tailwind.config.ts</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">/** @type {import('tailwindcss').Config} */</span>
<span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = {
  content: [<span class="hljs-string">"./src/**/*.{js,jsx,ts,tsx}"</span>],
  darkMode: <span class="hljs-string">"class"</span>, <span class="hljs-comment">//For our dark mode functionality</span>
  theme: {
    container: {
      center: <span class="hljs-literal">true</span>,
      padding: <span class="hljs-string">"1.25rem"</span>,
      screens: {
        sm: <span class="hljs-string">"1200px"</span>,
      },
    },
    extend: {},
  },
  plugins: [],
};
</code></pre>
<p>Add TailwindCSS to <code>src/index.css</code>:</p>
<pre><code class="lang-css"><span class="hljs-keyword">@tailwind</span> base;  
<span class="hljs-keyword">@tailwind</span> components;  
<span class="hljs-keyword">@tailwind</span> utilities;
</code></pre>
<h2 id="heading-step-2-how-to-set-up-internationalization-with-i18next">Step 2: How to Set Up Internationalization with i18next</h2>
<h3 id="heading-initialize-i18next"><strong>Initialize i18next</strong></h3>
<p>Create an <code>i18n.tsx</code> file in the <code>src</code> folder and configure i18next:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> i18next <span class="hljs-keyword">from</span> <span class="hljs-string">"i18next"</span>;
<span class="hljs-keyword">import</span> LanguageDetector <span class="hljs-keyword">from</span> <span class="hljs-string">"i18next-browser-languagedetector"</span>;
<span class="hljs-keyword">import</span> { initReactI18next } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-i18next"</span>;
<span class="hljs-keyword">import</span> Backend <span class="hljs-keyword">from</span> <span class="hljs-string">"i18next-http-backend"</span>;

i18next.use(LanguageDetector).use(initReactI18next).use(Backend).init({
  returnObjects: <span class="hljs-literal">true</span>,
  fallbackLng: <span class="hljs-string">"en"</span>, <span class="hljs-comment">// Language to fallback to if the selected is not configured</span>
  debug: <span class="hljs-literal">true</span>, <span class="hljs-comment">//To enable us see errors</span>
  <span class="hljs-comment">//   lng: "en", //Default language as english</span>
});
</code></pre>
<p>Let’s take a quick look at the contents of this file, as it plays a key role in enabling the translation functionality. This file is responsible for setting up the core of the translation process and making sure that the language-switching feature works smoothly across your app.</p>
<ul>
<li><p><code>i18next</code>: The core internalization library we’re using for translation.</p>
</li>
<li><p><code>LanguageDetector</code>: Helps us detect the user's preferred language automatically, based on browser settings.</p>
</li>
<li><p><code>initReactI18next</code>: Is responsible for integrating the <code>i18next</code> plugin with React and provides Hooks like the <code>useTranslation</code> Hook and other utilities.</p>
</li>
<li><p><code>Backend</code>: Fetches translation data dynamically from an external source. In this case, we’ll be using JSON files.</p>
</li>
</ul>
<p>Import this file into the <code>main.tsx</code> file:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">//main.tsx</span>

<span class="hljs-keyword">import</span> React, { StrictMode } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { createRoot } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-dom/client"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./index.css"</span>;
<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">"./App.tsx"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./i18n.tsx"</span>;  <span class="hljs-comment">//Import here</span>

createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"root"</span>)!).render(
  &lt;StrictMode&gt;
    &lt;React.Suspense fallback=<span class="hljs-string">"loading"</span>&gt;
      &lt;App /&gt;
    &lt;/React.Suspense&gt;
  &lt;/StrictMode&gt;
);
</code></pre>
<h3 id="heading-create-translation-files"><strong>Create Translation Files</strong></h3>
<p>In the <code>public/locales</code> directory, create subfolders for each language (for example, <code>en</code>, <code>fr</code>) and include <code>translation.json</code> files:</p>
<p><code>en/translation.json</code></p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"greeting"</span>: <span class="hljs-string">"Welcome to the Language Playground"</span>,
    <span class="hljs-attr">"detail"</span>: {
        <span class="hljs-attr">"line1"</span>: <span class="hljs-string">"Did you know that over 7,000 languages are spoken worldwide?"</span>,
        <span class="hljs-attr">"line2"</span>: <span class="hljs-string">"This Playground demonstrates how web applications can support users in multiple languages, making them accessible and inclusive to people from different backgrounds."</span>
    }
}
</code></pre>
<p><code>fr/translation.json</code></p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"greeting"</span>: <span class="hljs-string">"Bienvenue sur le terrain de jeu linguistique"</span>,
    <span class="hljs-attr">"detail"</span>: {
        <span class="hljs-attr">"line1"</span>: <span class="hljs-string">"Saviez-vous que plus de 7 000 langues sont parlées dans le monde ?"</span>,
        <span class="hljs-attr">"line2"</span>: <span class="hljs-string">"Ce terrain de jeu démontre comment les applications web peuvent prendre en charge les utilisateurs dans plusieurs langues, les rendant accessibles et inclusives aux personnes de différents horizons."</span>
    }
}
</code></pre>
<p>Here, you can add as many languages with their translation files that will be supplied to <code>i18next</code>. Note that the keys in the JSON files are the same as these would be used as references when displaying them on the website.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733095771585/21cbb0f9-e767-426e-8fc6-2bb4e7abc5ef.png" alt="Image of folder structure of translation files" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<h2 id="heading-step-3-how-to-build-components">Step 3: How to Build Components</h2>
<p>Create a <code>components</code> folder in the <code>src</code> directory and add the following components:</p>
<h3 id="heading-language-selector"><strong>Language Selector</strong></h3>
<p>Create the <code>LanguageSelector</code> component – contains a <code>select</code> element to help users switch languages dynamically:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> i18next <span class="hljs-keyword">from</span> <span class="hljs-string">"i18next"</span>;
<span class="hljs-keyword">import</span> { useTranslation } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-i18next"</span>;

<span class="hljs-keyword">type</span> languageOption = { language: <span class="hljs-built_in">string</span>; code: <span class="hljs-built_in">string</span> };

<span class="hljs-keyword">const</span> languageOptions: languageOption[] = [
  {
    language: <span class="hljs-string">"English"</span>,
    code: <span class="hljs-string">"en"</span>,
  },
  { language: <span class="hljs-string">"French"</span>, code: <span class="hljs-string">"fr"</span> },
  { language: <span class="hljs-string">"German"</span>, code: <span class="hljs-string">"de"</span> },
  { language: <span class="hljs-string">"Spanish"</span>, code: <span class="hljs-string">"es"</span> },
  { language: <span class="hljs-string">"Arabic"</span>, code: <span class="hljs-string">"ar"</span> },
  { language: <span class="hljs-string">"Yoruba"</span>, code: <span class="hljs-string">"yo"</span> },
];

<span class="hljs-keyword">const</span> LanguageSelector = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// Set the initial language from i18next's detected or default language</span>
  <span class="hljs-keyword">const</span> [language, setLanguage] = useState(i18next.language);

  <span class="hljs-keyword">const</span> { i18n } = useTranslation();

  <span class="hljs-keyword">const</span> handleLanguageChange = <span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLSelectElement&gt;</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> selectedLanguage = e.target.value;
    setLanguage(selectedLanguage);
    i18next.changeLanguage(selectedLanguage); <span class="hljs-comment">// Update language in i18next</span>
  };

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">document</span>.body.dir = i18n.dir(); <span class="hljs-comment">//sets the body to ltr or rtl</span>
  }, [i18n, i18n.language]);

  <span class="hljs-keyword">return</span> (
    &lt;select
      id=<span class="hljs-string">"language"</span>
      value={language}
      onChange={handleLanguageChange}
      className=<span class="hljs-string">"p-2 border border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring focus:ring-indigo-200 focus:ring-opacity-50
        dark:bg-gray-800 dark:border-gray-600 dark:text-gray-200 dark:focus:border-indigo-400 dark:focus:ring-indigo-700 dark:focus:ring-opacity-50"</span>
    &gt;
      {languageOptions.map(<span class="hljs-function">(<span class="hljs-params">{ language, code }, key</span>) =&gt;</span> (
        &lt;option value={code} key={key}&gt;
          {language}
        &lt;/option&gt;
      ))}
    &lt;/select&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> LanguageSelector;
</code></pre>
<ul>
<li><p>Initialize the language with the language detected by <code>i18next</code> or the default set language.</p>
</li>
<li><p>The <code>useTranslation</code> Hook exposes <code>i18n</code> instance from <code>i18next</code> to interact with the internationalization settings.</p>
</li>
<li><p>The <code>handleLanguageChange</code> function would be used to update the language selected by the user. It’s triggered when the user selects a new language from the dropdown menu.</p>
</li>
</ul>
<h3 id="heading-implementing-text-direction"><strong>Implementing Text Direction</strong></h3>
<p>The <code>dir</code> attribute in HTML is a critical feature for ensuring accessibility and inclusivity in web applications, especially when dealing with languages that differ in text direction. For example:</p>
<ul>
<li><p><strong>Left-to-Right (LTR)</strong>: Most languages, including English, French, and Spanish, follow this direction.</p>
<p>  <strong>Right-to-Left (RTL)</strong>: Languages like Arabic, and Hebrew require text alignment and layout to be flipped to maintain readability and cultural context.</p>
</li>
</ul>
<p>To achieve this in our app, we set the <code>document.body.dir</code> to the <code>dir</code> from <code>i18n</code> while listening for changes in language selection using the <code>useEffect</code> hook</p>
<h3 id="heading-dark-mode-toggle"><strong>Dark Mode Toggle</strong></h3>
<p>Create the <code>DarkModeToggle</code> component to switch between light and dark mode as preferred by the user.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { SunIcon, MoonIcon } <span class="hljs-keyword">from</span> <span class="hljs-string">"@heroicons/react/solid"</span>;

<span class="hljs-keyword">const</span> DarkModeToggle = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [darkMode, setDarkMode] = useState(<span class="hljs-literal">false</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-comment">// Check local storage or system preference on first load</span>
    <span class="hljs-keyword">const</span> isDark =
      <span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">"theme"</span>) === <span class="hljs-string">"dark"</span> ||
      (!<span class="hljs-built_in">localStorage</span>.getItem(<span class="hljs-string">"theme"</span>) &amp;&amp;
        <span class="hljs-built_in">window</span>.matchMedia(<span class="hljs-string">"(prefers-color-scheme: dark)"</span>).matches);
    setDarkMode(isDark);
    <span class="hljs-built_in">document</span>.documentElement.classList.toggle(<span class="hljs-string">"dark"</span>, isDark);
  }, []);

  <span class="hljs-keyword">const</span> toggleDarkMode = <span class="hljs-function">() =&gt;</span> {
    setDarkMode(!darkMode);
    <span class="hljs-built_in">document</span>.documentElement.classList.toggle(<span class="hljs-string">"dark"</span>, !darkMode);
    <span class="hljs-built_in">localStorage</span>.setItem(<span class="hljs-string">"theme"</span>, !darkMode ? <span class="hljs-string">"dark"</span> : <span class="hljs-string">"light"</span>);
  };

  <span class="hljs-keyword">return</span> (
    &lt;button
      aria-label=<span class="hljs-string">"Toggle dark mode"</span>
      onClick={toggleDarkMode}
      className=<span class="hljs-string">"p-1 rounded"</span>
    &gt;
      {darkMode ? (
        &lt;SunIcon
          className=<span class="hljs-string">"w-6 h-6 text-yellow-500 "</span>
          onClick={toggleDarkMode}
        /&gt;
      ) : (
        &lt;MoonIcon className=<span class="hljs-string">"w-6 h-6 text-gray-900 "</span> onClick={toggleDarkMode} /&gt;
      )}
    &lt;/button&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> DarkModeToggle;
</code></pre>
<h3 id="heading-header-component"><strong>Header Component</strong></h3>
<p>The <code>Header</code> component serves as a parent component to the <code>DarkModeToggle</code> and <code>languageSelector</code> components.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> DarkModeToggle <span class="hljs-keyword">from</span> <span class="hljs-string">"./DarkModeToggle"</span>;
<span class="hljs-keyword">import</span> LanguageSelector <span class="hljs-keyword">from</span> <span class="hljs-string">"./LanguageSelector"</span>;

<span class="hljs-keyword">const</span> Header = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    &lt;header className=<span class="hljs-string">"container flex justify-between"</span>&gt;
      &lt;DarkModeToggle /&gt;
      &lt;LanguageSelector /&gt;
    &lt;/header&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Header;
</code></pre>
<h2 id="heading-step-4-main-app-component">Step 4: Main App Component</h2>
<p>In the <code>src/app</code> file, include the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useTranslation } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-i18next"</span>;
<span class="hljs-keyword">import</span> Header <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/Header"</span>;

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> { t } = useTranslation();

  <span class="hljs-keyword">const</span> line1 = t(<span class="hljs-string">"detail.line1"</span>);
  <span class="hljs-keyword">const</span> line2 = t(<span class="hljs-string">"detail.line2"</span>);

  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"h-[100vh] bg-white text-black dark:bg-gray-900 dark:text-white py-8"</span>&gt;
      &lt;Header /&gt;
      &lt;div className=<span class="hljs-string">"container text-center max-w-2xl mt-28"</span>&gt;
        &lt;h1 className=<span class="hljs-string">"text-4xl font-bold"</span>&gt;{t(<span class="hljs-string">"greeting"</span>)}&lt;/h1&gt;
        &lt;p className=<span class="hljs-string">"mt-8"</span>&gt;{line1}&lt;/p&gt;
        &lt;p className=<span class="hljs-string">"mt-2"</span>&gt;{line2}&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<ul>
<li><p>The <code>useTranslation</code> Hook from <code>react-i18next</code> exposes the <code>t</code> function, which is used to fetch translated text.</p>
</li>
<li><p>It fetches the translated string based on a key from your translation files (for example, <code>en.json</code>, <code>fr.json</code>).</p>
</li>
</ul>
<p>By following these steps, your app should now be fully functional with translations seamlessly integrated. This is what the final result of our app looks like:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1733099671232/67419cbe-ed58-4bb4-9e44-67de2ffa9be4.png" alt="Image of the app running on localhost" class="image--center mx-auto" width="600" height="400" loading="lazy"></p>
<p>Check out the <a target="_blank" href="https://multilingual-demo.vercel.app/">live demo</a> and the source code on <a target="_blank" href="https://github.com/timmy471/multilingual-demo">GitHub</a></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Creating websites that give users the flexibility to select their preferred language is not just a technical achievement but a step toward making the web more inclusive and welcoming.</p>
<p>By combining internationalization (i18n) with tools like React-i18next and styling with Tailwind CSS, you can build applications that are flexible, user-friendly, and accessible to a global audience.</p>
<p>In this project, we walked through setting up i18n, adding a language switcher, and including “dark mode” for better usability.</p>
<h2 id="heading-references">References</h2>
<p><a target="_blank" href="https://react.i18next.com/">https://react.i18next.com/</a></p>
<p><a target="_blank" href="https://www.youtube.com/watch?v=dltHi9GWMIo">https://www.youtube.com/watch?v=dltHi9GWMIo</a></p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
