Build an AI LinkedIn Generator
Build a high-utility personal branding tool that transforms rough professional ideas into viral hooks, structured post bodies, hashtags, and first comments using structured JSON AI outputs.
Content Parameters
Generated Output
Copy ReadyWant to Test This Generator Live?
Launch our pre-configured sandbox with ready-to-run JSON schema routes and UI clipboard bindings.
The 30-Second Recipe
An AI content generator relies on strict JSON schema enforcement to split raw thoughts into modular publishing blocks.
Instead of asking an LLM to write a generic blog post, structure your backend request to return a typed JSON object containing distinct fields for the hook, the narrative body, suggested hashtags, and a priming first comment. This lets your React UI render each element inside dedicated copy-ready containers.
Topology
End-to-End System Architecture
Here is how data flows from user form submission to copy-ready social cards:
Parameter Capture
User submits raw topic, target audience, tone archetype, and personal anecdote via a structured control form.
Prompt Template Assembly
Backend wraps inputs into a specialized formatting prompt designed to enforce hook variety and readability spacing.
Structured JSON Inference
Model generates output under strict JSON response formats, guaranteeing separate fields for hook, body, and tags.
Schema Parsing & Validation
Backend parses and validates JSON payload to prevent broken markdown strings or unescaped characters.
Interactive UI Workspace
Frontend renders copy-ready cards for hooks, post body, hashtags, and first comments with 1-click clipboard states.
Step 1 · Backend Infrastructure
The Structured JSON API Route
Create the backend route handler at app/api/generate-linkedin/route.ts. It uses OpenAI Structured Outputs to guarantee a valid JSON response matching our publishing schema:
import OpenAI from "openai";
import { z } from "zod";
import { zodResponseFormat } from "openai/helpers/zod";
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
// Define strict output schema for structured generation
const LinkedInPostSchema = z.object({
hook: z.string().describe("A high-impact opening hook under 120 characters"),
body: z.string().describe("The main post narrative formatted with short 1-2 sentence paragraphs"),
hashtags: z.array(z.string()).describe("3 to 5 relevant professional hashtags"),
firstComment: z.string().describe("An engaging prompt or link teaser for the comment section"),
});
export async function POST(req: Request) {
try {
const { topic, tone, audience } = await req.json();
if (!topic) {
return new Response(JSON.stringify({ error: "Topic is required" }), { status: 400 });
}
const completion = await openai.beta.chat.completions.parse({
model: "gpt-4o",
messages: [
{
role: "system",
content: "You are an elite LinkedIn ghostwriter. Write engaging, skimmable professional content optimized for mobile feeds.",
},
{
role: "user",
content: `Topic: ${topic}\nTone: ${tone || "Professional"}\nTarget Audience: ${audience || "Founders & Engineers"}`,
},
],
response_format: zodResponseFormat(LinkedInPostSchema, "linkedin_post"),
});
const result = completion.choices[0].message.parsed;
return new Response(JSON.stringify(result), {
headers: { "Content-Type": "application/json" },
});
} catch (error) {
console.error("Generation error:", error);
return new Response(JSON.stringify({ error: "Failed to generate post" }), { status: 500 });
}
}Step 2 · Frontend Implementation
The Generator UI & Clipboard State
Create the client workspace component at components/tools/LinkedInGenerator.tsx to display generated cards with one-click clipboard copying:
"use client";
import { useState } from "react";
interface PostResult {
hook: string;
body: string;
hashtags: string[];
firstComment: string;
}
export default function LinkedInGenerator() {
const [topic, setTopic] = useState("");
const [tone, setTone] = useState("Provocative");
const [loading, setLoading] = useState(false);
const [post, setPost] = useState<PostResult | null>(null);
const [copiedField, setCopiedField] = useState<string | null>(null);
const handleGenerate = async (e: React.FormEvent) => {
e.preventDefault();
if (!topic.trim() || loading) return;
setLoading(true);
try {
const res = await fetch("/api/generate-linkedin", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ topic, tone }),
});
const data = await res.json();
setPost(data);
} catch (err) {
console.error(err);
} finally {
setLoading(false);
}
};
const copyToClipboard = (text: string, field: string) => {
navigator.clipboard.writeText(text);
setCopiedField(field);
setTimeout(() => setCopiedField(null), 2000);
};
return (
<div className="grid gap-6 md:grid-cols-2">
<form onSubmit={handleGenerate} className="space-y-4 p-6 border border-[var(--app-border)] rounded-2xl bg-[var(--app-card)] shadow-sm">
<h3 className="text-sm font-black text-[var(--app-text)]">Configure Post Parameters</h3>
<div>
<label className="text-xs text-[var(--app-muted)] block mb-1">Topic or Lesson</label>
<textarea
value={topic}
onChange={(e) => setTopic(e.target.value)}
placeholder="e.g., Why remote engineering teams fail without async docs..."
className="w-full bg-[var(--app-bg)] border border-[var(--app-border)] rounded-xl p-3 text-xs text-[var(--app-text)] h-28 focus:ring-1 focus:ring-amber-500"
/>
</div>
<div>
<label className="text-xs text-[var(--app-muted)] block mb-1">Tone</label>
<select
value={tone}
onChange={(e) => setTone(e.target.value)}
className="w-full bg-[var(--app-bg)] border border-[var(--app-border)] rounded-xl p-3 text-xs text-[var(--app-text)]"
>
<option>Provocative</option>
<option>Analytical</option>
<option>Storyteller</option>
<option>Direct & Concise</option>
</select>
</div>
<button
type="submit"
disabled={loading || !topic.trim()}
className="w-full bg-amber-500 hover:bg-amber-400 disabled:opacity-50 text-slate-950 font-bold py-3 rounded-xl text-xs transition shadow-sm"
>
{loading ? "Generating Post..." : "Generate LinkedIn Bundle ⚡"}
</button>
</form>
<div className="space-y-4">
{post ? (
<div className="p-6 border border-[var(--app-border)] rounded-2xl bg-[var(--app-card)] space-y-4 shadow-sm">
<div className="flex justify-between items-center">
<span className="text-xs font-bold text-emerald-600 dark:text-emerald-400 uppercase">Generated Results</span>
<button
onClick={() => copyToClipboard(`${post.hook}\n\n${post.body}\n\n${post.hashtags.join(" ")}`, "all")}
className="text-[11px] bg-amber-500/10 text-amber-600 dark:text-amber-300 px-3 py-1 rounded-lg font-bold"
>
{copiedField === "all" ? "Copied All! ✓" : "Copy Full Post"}
</button>
</div>
<div className="p-3 rounded-xl bg-[var(--app-chip)]/40 border border-[var(--app-border)] text-xs">
<strong className="block text-[var(--app-muted)] text-[10px] uppercase mb-1">Hook</strong>
{post.hook}
</div>
<div className="p-3 rounded-xl bg-[var(--app-chip)]/40 border border-[var(--app-border)] text-xs whitespace-pre-wrap">
<strong className="block text-[var(--app-muted)] text-[10px] uppercase mb-1">Body</strong>
{post.body}
</div>
<div className="flex flex-wrap gap-1.5">
{post.hashtags.map((tag, idx) => (
<span key={idx} className="text-[11px] bg-sky-500/10 text-sky-700 dark:text-sky-300 px-2.5 py-1 rounded-lg font-mono">
{tag}
</span>
))}
</div>
</div>
) : (
<div className="h-full flex items-center justify-center p-12 border border-dashed border-[var(--app-border)] rounded-2xl text-center text-xs text-[var(--app-muted)]">
Configure your topic and click generate to inspect structured output cards.
</div>
)}
</div>
</div>
);
}Content Strategy
The Psychology of High-Converting Hooks
On LinkedIn, 80% of a post's performance is determined by the first two lines before the "see more" fold. Your generator prompt should enforce proven hook archetypes:
"Best practices are destroying your engineering velocity." Challenges conventional wisdom to trigger curiosity.
"We cut our LLM token costs by 72% in 48 hours using 1 architectural trick." Leverages concrete numbers.
"I made a $5,000 infrastructure mistake so you don't have to." Builds immediate vulnerability and trust.
Level Up
Hands-On Build Challenges
Ready to take this generator to production? Implement these three enhancements:
Allow users to paste 3 examples of their past posts so the generator learns their personal writing cadence.
Extend your Zod schema to generate slide-by-slide copy for PDF document carousels.
Implement a lightweight utility to format key headings in mathematical bold unicode for visual pop.
Release Gate
Production LinkedIn Generator Checklist
Key Takeaways
Structured AI outputs turn unstructured ideas into modular publishing tools.
Building an AI LinkedIn generator teaches you how to leverage Zod and OpenAI Structured Outputs to eliminate messy markdown parsing. By separating hooks, body copy, and hashtags into typed JSON fields, your React frontend can deliver an exceptionally polished, copy-ready workspace.