Olá pessoal, tudo bem? Hoje eu trouxe uma integração com o gemini e next pra criar um simples chat funcional e responsivo. A ideia aqui é fazer algo que seja simples de entender e implementar pra ser um dos teus primeiros projetos usando Gemini API.

Image description

🛠️ Tecnologias usadas:

⚡ Next.js
🎨 TailwindCSS – estilização moderna e responsiva
💬 Gemini API – modelo de linguagem da Google
💻 TypeScript – segurança e escalabilidade

Pra tu poder implementar esse projeto, pode dar fork no meu projeto no github aqui -> repo
e criar tua chave de api no -> aistudio

depois cria um arquivo chamado .env.local e coloca essa chave que foi gerada lá.

GEMINI_API_KEY=tua-chave-aqui

🧐 analisando o projeto:

src/app/api/gemini/route.ts

import { NextRequest, NextResponse } from "next/server";
import { GoogleGenAI } from "@google/genai";

const ERROR_MESSAGE_500 = {
  error: "Erro ao gerar mensagem. Verifique sua chave ou modelo.",
};
const MODEL = "gemini-2.0-flash";
const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY });

export async function POST(req: NextRequest) {
  const { message: contents } = await req.json();

  try {
    const result = await ai.models.generateContent({
      model: MODEL,
      contents,
    });

    return NextResponse.json(result.text);
  } catch (err) {
    console.error("Error:", err);
    return NextResponse.json(ERROR_MESSAGE_500, { status: 500 });
  }
}

Para que esse arquivo funcione, tu precisou usar o npm install @google/genai. O await ai.models.generateContent é a função que vai se comunicar com a api e trazer o conteúdo gerado de volta pra ti. Nela tu vai informar o model do gemini api que está usando, nesse caso a gente utilizou o gemini-2.0-flash.

src/components/ChatMessage.tsx

Já o componente ChatMessage é bem simples, sua função é saber se a mensagem foi gerada pela api ou pela usuária:

import ReactMarkdown from "react-markdown";

const ChatMessage = ({
  message,
  isUser,
}: {
  message: string;
  isUser: boolean;
}) => {
  return (
    <div className={`flex ${isUser ? "justify-end" : "justify-start"} mb-4`}>
      <div
        className={`p-3 rounded-lg max-w-xs prose ${
          isUser ? "bg-blue-500 text-white" : "bg-gray-200 text-black"
        }`}
      >
        <ReactMarkdown>{message}</ReactMarkdown>
      </div>
    </div>
  );
};

export default ChatMessage;

importante: ele também utiliza esse ReactMarkdown pra interpretar e deixar a UI bonitinha com o que foi recebido em markdown.

Por fim: src/components/Chat.tsx

import { useRef, useState } from "react";
import ChatMessage from "./ChatMessage";
import { Button } from "./ui/button";
import { Card, CardContent, CardTitle } from "./ui/card";
import { Input } from "./ui/input";

type Msg = {
  message: string;
  isUser: boolean;
};

const Chat = () => {
  const inputRef = useRef<HTMLInputElement>(null);
  const [messages, setMessages] = useState<Msg[]>([]);

  const handleSendMessage = (message: string | undefined) => {
    if (!message) return;

    setMessages((prev) => [...prev, { message, isUser: true }]);
    inputRef.current!.value = "";

    fetch("/api/gemini", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ message }),
    })
      .then((res) => res.json() as Promise<string>)
      .then((message) => {
        setMessages((prev) => [...prev, { message, isUser: false }]);
      })
      .catch((error) => {
        console.error("Error:", error);
      });
  };
  return (
    <Card className="mb-4 max-h-[90vh] flex flex-col">
      <CardContent className="flex flex-col max-h-[85vh]">
        <CardTitle className="pb-2"> Converse com gemini</CardTitle>
        <div className="flex flex-col overflow-y-scroll mb-4">
          {messages.map(({ message, isUser }, index) => (
            <ChatMessage message={message} isUser={isUser} key={index} />
          ))}
        </div>
        <form
          className="flex gap-2"
          onSubmit={(e) => {
            e.preventDefault();
            handleSendMessage(inputRef.current?.value);
          }}
        >
          <Input
            type="text"
            placeholder="Digite sua mensagem..."
            className="mb-4"
            ref={inputRef}
          />
          <Button className="bg-blue-500 text-white">Enviar</Button>
        </form>
      </CardContent>
    </Card>
  );
};

export default Chat;

Aqui nesse componente é onde as mensagens são digitadas, enviadas, recebidas e passadas pro ChatMessage.
o handleSendMessage é quem controla isso basicamente. Nele tu pode notar que existe controle de estado e chamada de api.

🤝 Contribuições:

O projetinho está bem simples e com potencial pra muitas features! Manda tua PR com algumas sugestões!

💬 Feedback:
Me chama aqui nos comentários, ou dá uma olhada no projeto completo no GitHub:

🔗 github.com/analiseburtet/chat-next-gemini