This tutorial is designed to guide you through building an AI-powered web application and showcase the potential of AI in everyday web development. Artificial Intelligence (AI) is revolutionizing modern web technology, making it more innovative and responsive. By incorporating AI, developers can enhance user experiences through features like real-time data analysis, personalized content recommendations, and advanced image recognition.
Next.js is a robust Reach framework that enables developers to quickly build server-side rendered and static web applications. It offers excellent performance, scalability, and a seamless developer experience. TensorFlow.js, on the other hand, TensorFlow.js is a Javascript library that allows you to train and run machine learning models directly in the browser.
By combining Next.js and TensorFlow.js, you can create sophisticated web applications that leverage the power of AI without needing extensive backend infrastructure.
By the end of this tutorial, you will have built a fully functional AI-powered web application capable of performing image recognition tasks. You'll gain hands-on experience with Next.js and TensorFlow.js, learning how to integrate machine learning models into a modern web framework.
This tutorial will equip you with the skills to start incorporating AI features into your projects, opening up new possibilities for innovation and user engagement.
It's common to place your projects in a Projects
directory within your home directory.
Open the VS Code at the top bar, select View
scroll down to the terminal, and create a folder called Projects
this into the terminal:
mkdir -p ~/Projects
and move to the project's folder by running:
cd Projects
Your projects would then be located in paths like:/home/your-username/Projects/my_project
(Linux)/Users/your-username/Projects/my_project
(Mac)
Windows Using Linux subsystem WSL
If you haven't installed Next.js yet, you can create a new Next.js project using the following command:
npx create-next-app ai-web-app
Test that the app is working as for now:
npm run dev
You will see the Next.js app on the page http://localhost:3000
. If it works, we can proceed.
npm install @tensorflow/tfjs @tensorflow-models/mobilenet
ai-web-app/
├── node_modules/
├── public/
├── src/
│ ├── pages/
│ │ ├── api/
│ │ │ └── hello.js
│ │ ├── _app.js
│ │ ├── _document.js
│ │ ├── index.js
│ ├── styles/
│ │ ├── globals.css
│ │ ├── Home.module.css
│ ├── utils/
│ │ └── imageProcessing.js
├── .gitignore
├── package.json
├── README.md
So, we have to add the following file:
Erase all the code and add the following ones:
Head
from next/head
: Used to modify the <head>
section of the HTML.styles
from ../styles/Home.module.css
: Imports the CSS module for styling.useState
from react
: React hook for state management.loadModel
and loadImage
from @/utils/imageProcessing
: Utility functions for loading the model and image.model
: State to store the loaded TensorFlow model.
predictions
: State to store the predictions made by the model.
import Head from "next/head"; import styles from "../styles/Home.module.css"; import { useState } from "react"; import { loadModel, loadImage } from "@/utils/imageProcessing";
export default function Home() { const [model, setModel] = useState(null); const [predictions, setPredictions] = useState([]);
Sets the predictions state with the results.
const handleAnalyzeClick = async () => { const fileInput = document.getElementById("image-upload"); const imageFile = fileInput.files[0];
if (!imageFile) {
alert("Please upload an image file.");
return;
}
try {
const image = await loadImage(imageFile);
const predictions = await model.classify(image);
setPredictions(predictions);
} catch (error) {
console.error('Error analyzing the image:', error);
}
Retrieving the Uploaded Image File:
const fileInput = document.getElementById("image-upload"); const imageFile = fileInput.files[0];
document.getElementById("image-upload");
: This retrieves the file input element from the DOM. This element is where users upload their images.const imageFile = fileInput.files[0];
: This gets the first file from the file input. The files
property is an array-like object, so we select the first file uploaded.Checking if an Image File is Uploaded:
if (!imageFile) { alert("Please upload an image file."); return; }
if (!imageFile)
: This checks if an image file is selected. If no file is selected, imageFile
will be null
or undefined
.alert("Please upload an image file.");
: If no file is selected, an alert message is displayed to the user.return;
: The Function exits early if no file is selected, preventing further execution.Loading the Image and Classifying It:
try { const image = await loadImage(imageFile); const predictions = await model.classify(image); setPredictions(predictions); } catch (error) { console.error('Error analyzing the image:', error); }
try { ... } catch (error) { ... }
: The try-catch
The block handles any errors during the image loading and classification process.
Loading the Image:
const image = await loadImage(imageFile);
loadImage(imageFile)
: This function is a utility that converts the image file into a format suitable for processing TensorFlow.js. It likely involves reading the file and creating an HTML image element or a TensorFlow.js tensor.await
: This keyword ensures that the Function waits for the image loading to complete before moving to the next step.const image =
: The loaded image is stored in the image Variable.const predictions = await model.classify(image);
model.classify(image)
: This method uses the TensorFlow.js model to classify the input image. It returns predictions about what the image contains.await
: This ensures the Function waits for the classification process to complete.const predictions =
: The classification results are stored in the predictions Variable.setPredictions(predictions);
setPredictions(predictions)
: This updates the predictions State with the new classification results. This triggers a re-render of the component, displaying the predictions to the user.
Handling Errors:
catch (error) { console.error('Error analyzing the image:', error); }
catch (error) { ... }: This block catches any errors that occur during the try block.console.error('Error analyzing the image:', error);: If an error occurs, it logs the error message to the console for debugging purposes.
Uses a useState
Hook to load the model when the component mounts.
Sets the loaded model into the state.
useState(() => { (async () => { try { const loadedModel = await loadModel(); setModel(loadedModel); } catch (error) { console.error('Error loading the model:', error); } })(); }, []);
useState
Hook that initializes and loads the TensorFlow.js model when the component mounts.loadModel
Function, which loads the model and sets it in the component's state using the setModel
Function.[]
ensures this effect runs only once when the component is first rendered.To begin building our AI-powered web application with Next.js and TensorFlow.js, we'll set up a basic layout using Next.js components. This initial structure will be the foundation for our application's user interface.
return (
<>
...
</>
<> ... </>
: This React Fragment allows multiple elements to be grouped without adding extra nodes to the DOM.
<div className={styles.container}>
...
</div>
<div className={styles.container}> ... </div>
: This div wraps the main content of the page and applies styling from the styles.container
Class.
<Head>
<title>AI-Powered Web App</title>
</Head>
<main className={styles.main}>
...
</main>
<main className={styles.main}> ... </main>
: This main element contains the primary content of the page and applies styling from the styles.main
class
<h1 className={styles.title}>AI-Powered Web Application</h1>
<p className={styles.description}>
Using Next.js and TensorFlow.js to show some AI model.
</p>
<h1 className={styles.title}> ... </h1>
: This heading displays the main title of the page with styling from the styles.title
Class.<p className={styles.description}> ... </p>
: This paragraph provides a brief description and is styled using the styles.description
Class.<div id="input-area">
<input type="file" className={styles.input} id="image-upload" />
<button className={styles.button} onClick={handleAnalyzeClick}>
Analyze Image
</button>
</div>
<div id="input-area"> ... </div>
: This div wraps the input elements for uploading and analyzing an image.<input type="file" className={styles.input} id="image-upload" />
: This input element allows users to upload an image file. It uses the styles.input
class for styling and has an ID of image-upload
.<button className={styles.button} onClick={handleAnalyzeClick}>Analyze Image</button>
: This button triggers the handleAnalyzeClick function
when clicked. It is styled using the styles.button
Class.<div id="output-area">
{predictions.length > 0 && (
<ul>
{predictions.map((pred, index) => (
<li key={index}>
{pred.className}: {(pred.probability * 100).toFixed(2)}%
</li>
))}
</ul>
)}
</div>
<div id="output-area"> ... </div>
: This div contains the output area where predictions are displayed.{predictions.length > 0 && ( ... )}
: This conditional rendering checks for predictions and renders the list of predictions if there are any.<ul> ... </ul>
: An unordered list that will contain the prediction items.predictions.map((pred, index) => ( ... ))
: This maps over the predictions Array and render each prediction as a list item.<li key={index}> ... </li>
: Each list item displays the class name and probability of the prediction, formatted to two decimal places. The key
attribute helps React identify which items have changedEdit the Styles for the index.js file in Home.module.css erase all the code, and add the following one:
.container {
min-height: 100vh;
padding: 0 0.5rem;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.main {
padding: 5rem 0;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.title {
margin: 0;
line-height: 1.15;
font-size: 4rem;
text-align: center;
}
.description {
margin: 4rem 0;
line-height: 1.5;
font-size: 1.5rem;
text-align: center;
}
#ouput-area {
margin-top: 2rem;
}
.li {
margin-top: 10px;
font-size: 20px;
}
.button {
margin-top: 1rem;
padding: 0.5rem 1rem;
font-size: 1rem;
cursor:pointer;
background-color: #0070f3;
color: white;
border: none;
border-radius: 5px;
}
.button:hover {
background-color: #005bb5;
}
Once you have done the previous steps, check to see something like this:
Now, let's work with the brain of the app. imageProcessing.js File:
import * as tf from "@tensorflow/tfjs";
import * as mobilenet from "@tensorflow-models/mobilenet";
export async function loadModel() {
try {
const model = await mobilenet.load();
return model;
} catch (error) {
console.error("Error loading the model:", error);
throw error;
}
}
This Function loads the MobileNet model using TensorFlow.js. Here's a step-by-step explanation:
tf
) and the MobileNet model (mobilenet
).loadModel
function is defined as an asynchronous function.await mobilenet.load()
.export function preprocesImage(image) {
const tensor = tf.browser
.fromPixels(image)
.resizeNearestNeighbor([224, 224]) // MobileNet input size
.toFloat()
.expandDims();
return tensor.div(127.5).sub(1); // Normalize to [-1, 1] range
}
This function preprocesses an image in the format required by MobileNet. Here's a step-by-step explanation:
preprocesImage function
is defined to take an image as an argument.tf.browser.fromPixels(image)
.[224, 224]
, which is the required input size for MobileNet..toFloat()
..expandDims()
.[-1, 1]
by dividing by 127.5
and subtracting 1
.export function loadImage(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.src = event.target.result;
img.onload = () => resolve(img);
};
reader.onerror = (error) => reject(error);
reader.readAsDataURL(file);
});
}
This Function loads an image file and returns an HTML Image element. Here's a step-by-step explanation:
loadImage function
is defined as taking a file as an argument.FileReader
Object is created to read the file.onload
event handler of the reader is defined. It creates a new Image Object sets its source to the file reader's result and resolves the promise with the image once it is loaded.onerror
event handler of the reader is defined to reject the promise with an error if one occurs. Read File: The file reader reads the file as a data URL using reader.readAsDataURL(file)
.Now, you can test this final project by uploading images to the project's page and seeing the final results; if you have any problems, please check the provided link to clone the project from Github:
This tutorial taught you how to build an AI-powered web application using Next.js and TensorFlow.js. We covered:
By combining Next.js and TensorFlow.js, you can create sophisticated web applications that leverage the power of AI, enhancing user experiences with features like image recognition.
To further improve your application, consider exploring these additional features:
Ivan Duarte is a backend developer with experience working freelance. He is passionate about web development and artificial intelligence and enjoys sharing their knowledge through tutorials and articles. Follow me on X, Github, and LinkedIn for more insights and updates.