This is a chat app powered by AI, built with Next.js, which is a robust framework for React, and Google's Gemini AI. The goal of this project was to develop a modern web application where users can engage with an AI assistant, similar to ChatGPT, but created from the ground up with a clean user interface and tidy code.
Even if you're just starting out with coding, this project is designed to be easy to follow. It guides you through:
- Setting up a Next.js project
- Connecting your frontend to the Gemini AI API
- Crafting a clean UI with reusable components
- Building and calling API routes in Next.js
- Using basic CSS to style your app
This project is fully functional and lightweight, making it ideal for beginners eager to learn how to blend AI with contemporary web development.
File Structure of Project
D:\Gext\my-chat-app
├── jsconfig.json
├── next.config.mjs
├── node_modules/
├── package-lock.json
├── package.json
├── postcss.config.mjs
├── public/
├── README.md
└── src/
├── components/
│ └── GeminiPrompt.js
├── pages/
│ ├── api/
└── gemini.js
│ ├── index.js
│ ├── _app.js
│ └── _document.js
└── styles/
└── globals.css
- To run the project, you just need to run `npm run dev`, which starts executing the script in package.json to launch the Next.js server.
- To access the homepage, navigate to `/`, where you will find the home page located at `src/pages/index.js`.
- For global CSS, the file `src/styles/globals.css` is included through `src/pages/_app.js`.
- Every time a page loads, `src/pages/_app.js` wraps all the pages with a shared layout or styles.
- If you need to set up an HTML structure, `src/pages/_document.js` defines the base HTML layout, but keep in mind it only runs on the server.
- When it comes to API requests, any file within `src/pages/api/` takes care of the backend logic, like `api/gemini.js`.
Setting up a Next.js project
Step-by-step list of commands to set up a Next.js project and install Google Gemini AI SDK
Create a new Next.js project
npx create-next-app@latest my-chat-app
Go into the project folder
cd my-chat-app
Install Gemini SDK (Google Generative AI)
npm install @google/generative-ai
Start the server
npm run dev
Building and calling API routes in Next.js
Create a .env.local file in the root folder and add your Gemini API key like this
GEMINI_API_KEY = Your_Api_Key
src/components/GeminiPrompt.js h
andles the user prompt input, sends it to the Gemini API via /api/gemini
, and displays the AI's response on the screen.
// src/components/GeminiPrompt.js
import React, { useState } from "react";
const GeminiPrompt = () => {
const [prompt, setPrompt] = useState(""); // user input
const [response, setResponse] = useState(""); // AI response
const handleSubmit = async (e) => {
e.preventDefault(); // stop page refresh
try {
const res = await fetch("/api/gemini", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ prompt }),
});
const data = await res.json(); // no need to JSON.parse again
setResponse(data?.text_content || "No response received.");
} catch (err) {
console.error(err);
setResponse("Something went wrong. Try again.");
}
};
return (
<div>
<h2>Ask Anything to Gemini</h2>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Type your prompt"
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
/>
<button type="submit">Send</button>
</form>
{response && (
<div>
<h3>Reply:</h3>
<p>{response}</p>
</div>
)}
</div>
);
};
export default GeminiPrompt;
src/pages/_app.js is the file where global styles are loaded and where every page is wrapped in a common layout via the App component. This is critical in order to apply styles from globals.css to the entire app.
import "@/styles/globals.css";
export default function App({ Component, pageProps }) {
return <Component {...pageProps} />;
}
src/pages/_document.js sets the basic HTML structure (that is, , , ). This is useful for meta tags, fonts, language, etc., and only runs server-side.
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
src/pages/index.js is the homepage (/) of the app and is the first page that users see when they open the site. It contains the GeminiPrompt component that allows the user to interact with the AI.
import GeminiPrompt from "@/components/GeminiPrompt";
export default function Home() {
return (
<div>
<GeminiPrompt />
</div>
);
}
src/pages/api/gemini.js route is designed to fetch the GEMINI_API_KEY, make a call to Google Gemini 1.5-flash using the user's prompt, and then return the generated text content (or an error/blocked message) in JSON format.
import { GoogleGenerativeAI } from "@google/generative-ai";
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
/**
* api key from env
*/
const apiKey = process.env.GEMINI_API_KEY;
console.log("apiKey", apiKey);
export default async function handler(req, res) {
if (req.method === "POST") {
const { prompt } = req.body;
if (prompt === "") {
return res.status(400).json({ error: "fill all fields" });
}
try {
const genAI = new GoogleGenerativeAI(apiKey);
const model = genAI.getGenerativeModel({
model: "gemini-1.5-flash",
systemInstruction: {
parts: [
{
text: `You are a helpful assistant. Please be concise and precise.
**Your response must always be a valid JSON object with the following structure:
* **text_content:** the generated content.
`,
},
],
role: "model",
},
});
const parts = [{ text: prompt }];
/**
* generation config for gemini api calls
* setting responseMimeType to JSON to get back response in json format
*/
const generationConfig = {
temperature: 1,
topP: 0.95,
topK: 64,
maxOutputTokens: 8192,
responseMimeType: "application/json",
};
const result = await model.generateContent({
contents: [{ role: "user", parts }],
generationConfig,
});
let response = "";
if (
result.response.promptFeedback &&
result.response.promptFeedback.blockReason
) {
response = {
error: `Blocked for ${result.response.promptFeedback.blockReason}`,
};
return res.status(200).json(response);
}
response = result.response.candidates[0].content.parts[0].text;
return res.status(200).json(response);
} catch (error) {
console.error(error);
return res
.status(500)
.json({ error: "Failed to get a response from Gemini" });
}
} else {
return res.status(405).json({ message: "Method not allowed" });
}
}
App Response
![App response]()