Current Section

Overview

0%

← Back to Real Projects
Real Projects · Project 6

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.

Next.js App RouterStructured JSON SchemaPersonal BrandingProduction Recipe
linkedin-generator-preview.tsx
Live UI Preview

Content Parameters

Why sliding window memory saves AI API costs.
Provocative & Analytical
Generate Post Bundle ⚡

Generated Output

Copy Ready
"Passing entire conversation histories to an LLM is a silent financial trap."
Here is why your AI app gets 20x more expensive by turn 25... [Read more]
#AIEngineering#NextJS
AIMates Hands-On Lab

Want to Test This Generator Live?

Launch our pre-configured sandbox with ready-to-run JSON schema routes and UI clipboard bindings.

Launch Sandbox Lab →

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.

User Input Form → POST /api/generate → Pydantic/JSON Schema → Parsed Multi-Card UI

Topology

End-to-End System Architecture

Here is how data flows from user form submission to copy-ready social cards:

01

Parameter Capture

User submits raw topic, target audience, tone archetype, and personal anecdote via a structured control form.

02

Prompt Template Assembly

Backend wraps inputs into a specialized formatting prompt designed to enforce hook variety and readability spacing.

03

Structured JSON Inference

Model generates output under strict JSON response formats, guaranteeing separate fields for hook, body, and tags.

04

Schema Parsing & Validation

Backend parses and validates JSON payload to prevent broken markdown strings or unescaped characters.

05

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:

app/api/generate-linkedin/route.ts
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:

components/tools/LinkedInGenerator.tsx
"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 &amp; 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:

1. Counter-Intuitive

"Best practices are destroying your engineering velocity." Challenges conventional wisdom to trigger curiosity.

2. Metric Proof

"We cut our LLM token costs by 72% in 48 hours using 1 architectural trick." Leverages concrete numbers.

3. Confessional

"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:

Challenge 1: Brand Voice Memory

Allow users to paste 3 examples of their past posts so the generator learns their personal writing cadence.

Challenge 2: Carousel Outlines

Extend your Zod schema to generate slide-by-slide copy for PDF document carousels.

Challenge 3: Unicode Styling

Implement a lightweight utility to format key headings in mathematical bold unicode for visual pop.

Release Gate

Production LinkedIn Generator Checklist

✓Outputs are guaranteed via strict JSON Schema parsing so UI components never break on malformed markdown.
✓UI provides instant 1-click copy feedback states for hooks, main post text, and hashtags.
✓Inputs are validated at the edge to prevent empty topic submissions or prompt injection overflows.
✓Tone adjustments (Provocative, Analytical, Storyteller) dynamically alter prompt templates on the fly.
✓Generated posts maintain optimal LinkedIn readability spacing (1-2 sentence paragraph blocks).
✓API keys are sequestered securely in server environment variables, never bundled into browser bundles.
✓Character and line counts are displayed to ensure posts stay well within optimal mobile feed limits.

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.

Parameter Form → Zod Structured Schema → Typed JSON Response → 1-Click Clipboard Cards.