Building a GPT-3 Twitter bio generator with Next.js
GPT-3 has opened up a new class of applications that generate human-like text on demand. A practical example is twitterbio.com, which takes a user's existing bio or a few descriptive sentences and produces new biography options in a chosen tone. The entire application—frontend UI, API route, and OpenAI integration—can live in a single Next.js project.
Frontend components and state
The user interface is straightforward: a text area for the user's current bio, a dropdown for selecting tone, a submit button, and two containers for displaying the generated results. The page maintains state for the input text, the selected tone, loading status, and the generated bios returned from the API.
The core logic mirrors how ChatGPT works—a prompt must be constructed and sent to GPT-3. The prompt concatenates the user's bio and selected tone, then instructs the model to return exactly two bios in a numbered format so the client can split them cleanly for display.
const prompt = `Generate 2 ${vibe} twitter bios with no hashtags and clearly labeled "1."
and "2.". Make sure each generated bio is at least 14 words and at max 20 words and base
them on this context: ${bio}`;
Beyond the form elements themselves, the page includes a generateBio function that fires on submit. It sends a POST request to the /api/generate route with the prompt in the request body.
<textarea
value={bio}
onChange={(e) => setBio(e.target.value)}
rows={4}
className="..."
placeholder={"e.g. Senior Engineer @vercel. Tweeting about web dev & AI."}
/>
<div className="...">
<Image src="/2-black.png" width={30} height={30} alt="1 icon" />
<p className="...">Select your vibe.</p>
</div>
<div className="block">
<DropDown vibe={vibe} setVibe={(newVibe) => setVibe(newVibe)} />
</div>
<button className="..." onClick={(e) => generateBio(e)}>
Generate your bio →
</button>
<hr className="..." />
<div className="...">
{generatedBios && (
<>
<div>
<h2 className="...">Your generated bios</h2>
</div>
<div className="...">
{generatedBios
.substring(generatedBios.indexOf("1") + 3)
.split("2.")
.map((generatedBio: any) => {
return (
<div className="..." key={generatedBio}>
<p>{generatedBio}</p>
</div>
);
})}
</div>
</>
)}
</div>
Once the API responds, the generated text is stored in the generatedBios state and rendered to the user. Because the prompt asks GPT-3 to output the two responses with explicit numbering, the client can split the response on the "2." delimiter to display each bio in its own container.
const generateBio = async (e: any) => {
e.preventDefault();
setGeneratedBios("");
setLoading(true);
const response = await fetch("/api/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt,
}),
});
if (!response.ok) {
throw new Error(response.statusText);
}
let answer = await response.json();
setGeneratedBios(answer.choices[0].text);
setLoading(false);
};
API route and OpenAI payload
Next.js makes it possible to handle frontend and backend in a single deployment. Creating a file named generate.ts inside the api folder is enough to expose a new endpoint. The route reads the prompt from the incoming request body, then builds a payload for OpenAI that specifies the GPT-3 model and a maximum token count. Since Twitter bios have a strict character limit, capping tokens prevents the model from generating overly long responses.
The payload is sent to OpenAI with a POST request. The route awaits the response, extracts the generated bios, and returns them to the client as JSON.
export default async function handler(req, res) {
const { prompt } = req.body;
const payload = {
model: "text-davinci-003",
prompt,
temperature: 0.7,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0,
max_tokens: 200,
n: 1,
};
const response = await fetch("https://api.openai.com/v1/completions", {
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ""}`,
},
method: "POST",
body: JSON.stringify(payload),
});
const json = await response.json();
res.status(200).json(json);
}
Current guidance for new projects
This walkthrough reflects how the example was originally built. The approach has since evolved. For new AI applications on Vercel, the recommended pattern is Vercel Functions with the default Node.js runtime and Fluid compute.
Rebuilding this example today means following a few key steps:
- Keep the API route on the Node.js runtime rather than an edge runtime.
- Stream model output from the route to the client for a better user experience.
- Use Fluid compute to handle long-running model calls and manage concurrency efficiently.
- Select a function region close to the upstream AI provider when response latency is a priority.
For implementation details, refer to the Vercel Functions and Streaming Functions documentation.
The template has been used to build several production applications, including Rephraser, GenzTranslator, and ChefGPT. The full source code for the serverless version of this example and a live demo are available for reference.



