π Let's Build an AI LinkedIn Post Generator project using Gemini AI, Next.js, and TailwindCSS π
Table of contents
2025 is here, and what a time to be alive, what better way to kick off the year than by building an awesome LinkedIn Post project? π― In this blog, Iβll show you how to integrate the Gemini API with Next.js and style it using TailwindCSS, to make it more interesting lets create our UI using V0.dev. Plus, we'll use the Gemini API Key to fetch posts results and display them.
Now,Letβs dive in! π₯
Prerequisites π
Before we get started, make sure you have:
Node.js installed
A Gemini API key (set up at Gemini for key)
Familiarity with Next.js/React.js and TailwindCSS
Implementation
1. Create a Next.js Project π₯οΈ
Start by creating a new Next.js project:
npx create-next-app linkedin-wizard
cd linkedin-wizard
2. Install Gemini API Package π¦
npm i @google/generative-ai
Create a .env
file in the root directory and add your Gemini API key:
GEMINI_API_KEY=your_api_key_here
3. Fetch Twitter Posts with Gemini API π₯
Create app/api/generatepost/route.ts
path in project,In route.ts we will fetch the Twitter-like posts using the Gemini API and display them.
import { GoogleGenerativeAI } from "@google/generative-ai";
import { NextResponse } from "next/server";
const API_KEY = process.env.GEMINI_API_KEY || "";
export async function POST(req: Request) {
const { description, wordlimit = 100 } = await req.json();
if (!description) {
return NextResponse.json(
{ error: "Description is required." },
{ status: 400 }
);
}
try {
const genAI = new GoogleGenerativeAI(API_KEY);
const model = await genAI.getGenerativeModel({ model: "gemini-1.5-pro" });
const prompt = `Generate a linkedpost post under ${wordlimit} words on the basis of this description: ${description}`;
const result = await model.generateContent([prompt]);
if (result && result.response) {
const generatedText = await result.response.text();
return NextResponse.json({ post: generatedText });
} else {
throw new Error("No response received from model.");
}
} catch (error) {
console.error("Error generating post:", error);
return NextResponse.json(
{ error: "Failed to generate post" },
{ status: 500 }
);
}
}
Above code's functionality description is:
Generates Post: Takes a description and an optional wordlimit, uses Google's AI to create a tweet based on it.
Error Handling: Returns errors if no description is provided or if AI fails.
AI Model Used: Uses
gemini-1.5-pro
for content generation.
4. Main front-end logic of handling : generate post
, copy post
. regenerate post
is :
Lets open V0.dev to generate a UI for us and copy the generated UI to our code
"use client";
import { useState } from "react";
import { Loader2, Copy, RefreshCw, Linkedin } from "lucide-react";
export default function PostGenerator() {
const [description, setDescription] = useState("");
const [wordLimit, setWordLimit] = useState("");
const [generatedPost, setGeneratedPost] = useState("");
const [isLoading, setIsLoading] = useState(false);
const generatePost = async () => {
setIsLoading(true);
try {
const response = await fetch("/api/generatePost", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
description,
wordLimit: Number.parseInt(wordLimit) || undefined,
}),
});
const data = await response.json();
setGeneratedPost(data.post);
} catch (error) {
console.error("Error generating post:", error);
}
setIsLoading(false);
};
const copyToClipboard = () => {
navigator.clipboard.writeText(generatedPost);
alert("copied post to clipboard");
};
return (
<div className="max-w-2xl mx-auto p-4 space-y-6">
<h1 className="text-2xl font-bold flex justify-center items-center">
<Linkedin />
<div className="ml-2 pt-1">Post Generator</div>
</h1>
<div className="space-y-2">
<label
htmlFor="description"
className="block text-sm font-medium text-gray-700"
>
Description
</label>
<textarea
id="description"
placeholder="Enter post description"
value={description}
onChange={(e) => setDescription(e.target.value)}
className="w-full px-3 py-2 text-gray-700 border rounded-lg focus:outline-none focus:border-blue-500 min-h-[100px]"
/>
</div>
<div className="space-y-2">
<label
htmlFor="wordLimit"
className="block text-sm font-medium text-gray-700"
>
Word Limit (optional)
</label>
<input
id="wordLimit"
type="number"
placeholder="Enter word limit"
value={wordLimit}
onChange={(e) => setWordLimit(e.target.value)}
className="w-full px-3 py-2 text-gray-700 border rounded-lg focus:outline-none focus:border-blue-500"
/>
</div>
<div className="flex space-x-2">
<button
onClick={generatePost}
disabled={isLoading || !description}
className={`flex items-center justify-center px-4 py-2 text-white bg-blue-600 rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 ${
isLoading || !description ? "opacity-50 cursor-not-allowed" : ""
}`}
>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Generating...
</>
) : (
"Generate Post"
)}
</button>
<button
onClick={generatePost}
disabled={isLoading || !description}
className={`flex items-center justify-center px-4 py-2 text-blue-600 bg-white border border-blue-600 rounded-lg hover:bg-blue-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-opacity-50 ${
isLoading || !description ? "opacity-50 cursor-not-allowed" : ""
}`}
>
<RefreshCw className="mr-2 h-4 w-4" />
Regenerate
</button>
</div>
{generatedPost && (
<div className="space-y-2">
<label
htmlFor="generatedPost"
className="block text-sm font-medium text-gray-700"
>
Generated Post
</label>
<div className="relative">
<textarea
id="generatedPost"
value={generatedPost}
readOnly
className="w-full px-3 py-2 text-gray-700 border rounded-lg focus:outline-none focus:border-blue-500 min-h-[200px]"
/>
<button
onClick={copyToClipboard}
className="absolute top-2 right-2 p-2 text-gray-500 hover:text-gray-700 focus:outline-none"
>
<Copy className="h-4 w-4" />
</button>
</div>
</div>
)}
</div>
);
}
You can easily change colors, spacing, and other design elements using Tailwind classes.
5. Run the Project π
Now, itβs time to run your project:
npm run dev
Open http://localhost:3000
in your browser, and youβll see your linkedIn-like post feed in action! π
Output:
Conclusion
To get access to the code that I have used above, click here. For any query, you can get in touch with me via LinkedIn and twitter.Leave all your comments and suggestions in the comment section. Let's connect and share more ideas.
Happy coding!