# Building a GPT\-3 app with Next\.js and streaming \(historical\)

**Published:** February 1, 2023 | **Authors:** Hassan El Mghari | **Category:** Field Engineering

*A full\-stack template for building fast GPT\-3 apps\.*

---

> **Note:** This post is historical and describes Edge Functions as they existed at the time. For new projects, use [Vercel Functions](https://vercel.com/docs/functions) with the Node.js runtime and [Fluid compute](https://vercel.com/docs/fluid-compute).

The field of artificial intelligence continues to take the world by storm. Huge strides have been made in text and image generation through tools like ChatGPT, GPT-3, DALL-E, and Stable Diffusion. It’s spawned a wave of exciting AI startups, many of which we’re seeing [built with Vercel and Next.js](https://twitter.com/steventey/status/1610656840412696578?utm_source=vercel_site&utm_medium=blog&utm_campaign=blog_building_a_gpt3_app).

One of the most exciting developments in the AI space is GPT-3, a cutting-edge natural language processing model developed by [OpenAI](/docs/integrations/openai). With its ability to understand and generate human-like text, GPT-3 has the potential to disrupt how we perform many of our tasks.

This historical walkthrough uses [twitterbio.com](https://www.twitterbio.com/) to show how streaming changed the user experience for GPT-3 apps. For new projects, build this pattern with [Vercel Functions](https://vercel.com/docs/functions), [Fluid compute](https://vercel.com/docs/fluid-compute), and streaming responses.

## The frontend

The Next.js frontend consists of a few elements:

- A text box for users to copy their current bio or write a few sentences about themselves
- A dropdown where they can select the tone of the bio they want to be generated
- A submit button for generating their bio, which when clicked calls an API route that uses OpenAI’s GPT-3 model and returns two generated bios
- Two containers to display the generated bios after we get them back from the API route

Here’s what the code for our [index page](https://github.com/Nutlope/twitterbio/blob/serverless/pages/index.tsx) looks like. We have a few pieces of state that correspond to the elements mentioned above.  We’re also defining a prompt—like ChatGPT, we need to send a prompt to GPT-3 to instruct it to generate the new bios. Finally, we ask GPT-3 to generate two bios clearly labeled (so we can parse them correctly) using the user-provided bio and vibe as context.

```javascript
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}`;
```

The rest of our index page is comprised of the UI elements themselves: our text box, dropdown, submit button, and two containers on the bottom that we display when we get the generated bios. There's also some loading logic for the button to show a loading indicator when clicked.

**GenerateBio.tsx**
```tsx
<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 &rarr;
</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>
```

In addition to the UI elements and the loading logic, we have a `generateBio` function that’s called when the user clicks the submit button. This sends a `POST` request to our `/api/generate` API route with the prompt in the body.

**GenerateBio.tsx**
```tsx
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);
};
```

We get the generated bios back from the API route, save it to the `generatedBios` state, then display it to the user. Because we asked GPT-3 to return the text in a specific numbered format, we can split it based on the “2.” to show the user the two bios separated nicely into containers as seen below.

## The backend

A great advantage of using Next.js is being able to handle both our frontend and backend in a single application. We can spin up an API route just by creating a file called `generate.ts` in our `api` folder. Let’s take a look at our `/api/generate` API Route.

We get the prompt from the request body that’s passed in on the frontend, then construct a payload to send to OpenAI. In this payload, we specify some important information like the exact model (GPT-3) and how many tokens we want OpenAI to respond with (a token is approximately 4 characters). In this case, we’re limiting the max tokens because Twitter bios have a character constraint.

After the payload is constructed, we send it in a `POST` request to OpenAI, await the result to get back the generated bios, then send them back to the client as JSON.

**generate.js**
```javascript
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);
}
```

There we have it! We built the first version of our application. Feel free to check out the [code](https://github.com/Nutlope/twitterbio/tree/serverless) and [demo](https://twitterbio-git-serverless-randomteam.vercel.app/) for this approach.

## Current guidance: use Vercel Functions with streaming

The original version of this tutorial migrated the example to Edge Functions to get streaming and avoid timeouts. For new AI applications on Vercel, use [Vercel Functions](https://vercel.com/docs/functions) with the default Node.js runtime and [Fluid compute](https://vercel.com/docs/fluid-compute). This gives you streaming support, longer durations, and full Node.js API compatibility.

If you are rebuilding this example today:

- Keep the API route on the Node.js runtime.
- Stream model output from the route to the client.
- Use Fluid compute for long-running model calls and efficient concurrency.
- Choose a function region close to the upstream AI provider when latency matters.

See [Vercel Functions](https://vercel.com/docs/functions) and [Streaming Functions](https://vercel.com/docs/functions/streaming-functions) for current implementation guidance.

## Resources

We hope this walkthrough helps you build incredible GPT-3 powered applications. We’ve already seen several sites built with this template such as [Rephraser](https://twitter.com/jsonjun/status/1617739994529148928), [GenzTranslator](https://twitter.com/verfasor/status/1616861894069088256), and [ChefGPT](https://twitter.com/madeinusmate/status/1616698060398264323)—some of which have thousands of users. Visit the [Twitter Bio site](https://www.twitterbio.com/) to see everything we talked about in action, check out our [other AI templates](https://vercel.com/templates/ai), or start optimizing prompts across various models with Vercel's [AI Playground](https://play.vercel.ai/).

---

**More posts:** [View all blog posts](https://vercel.com/blog/sitemap.md) | [Changelog](https://vercel.com/changelog/sitemap.md)