Build a website with Next.js

Learn how to build a Next.js web application that uses Replicate's cloud API to run a machine learning model. By the end, youโ€™ll have your own deployed website that can accept text prompts as input and generate images using Stable Diffusion.

๐Ÿ‡ If you'd rather skip ahead to a working codebase, check out the GitHub repository at github.com/replicate/getting-started-nextjs.

Prerequisites

  • Node.js: You'll need Node.js installed to be able to run your application locally. The easiest way to install Node.js is by downloading and running the package installer at nodejs.org.
  • Replicate account: You'll use Replicate's cloud API to run machine learning models. It's free to get started, and you get a bit of credit when you sign up. After that, you pay per second for your usage. See pricing for more details.
  • GitHub account: This is where you'll host the source code for your application.
  • Vercel account: Vercel is a platform for hosting Next.js apps. This is where you'll deploy your web application.

Step 1: Create the app

Next.js is a framework for building web applications with JavaScript. You can use it to build apps that have both a Node.js backend web server and a React frontend. It's a great choice for building web applications that use Replicate's API because it's easy to get started with and it's easy to deploy to Vercel.

The easiest way to get started with a new Next.js app is to use the create-next-app command:

npx create-next-app@latest --js --eslint

This command asks you to choose a name for your project, then creates a project directory for you and installs the necessary dependencies. It also takes care of initializing a new Git repository and creating an initial commit with all the added files. This gives you a good starting point for managing your project's source code history.

Step 2: Run the app locally

Now run your app locally to make sure everything is working:

cd my-app
npm run dev

You should have a running starter app at this point. View it in your browser at localhost:3000.

Step 3: Configure your environment

You need your Replicate API token to be able to run models. You can set it as an environment variable in your local development environment. To get your API token, sign in to your Replicate account and go to the account page.

Next.js has built-in support for loading environment variables from a .env.local file into process.env.

Create a file called .env.local in the root of your project:

touch .env.local

Then edit the file and add your token to it:

REPLICATE_API_TOKEN=<paste-your-token-here>

Note: The npx create-next-app command you ran in Step 1 created a .gitignore file that ignores .env.local files. This is a good thing, because you don't want to accidentally commit your API token to your project's source code repository.

Step 4: Build the backend

Now it's time to write some server-side code that you'll use to run models with the Replicate API.

One of the great things about Next.js is that you can write your backend code in the same project as your frontend code. Any code that you write in the pages/api directory is treated as a Node.js backend API endpoint.

You'll create two server-side endpoints: One for creating a prediction and one for polling the status of that prediction until it's complete. To learn more about predictions, see How does Replicate work?.

Start by creating a directory for these endpoints:

mkdir -p pages/api/predictions

Now create a file to handle prediction creation requests. Call it pages/api/predictions/index.js and add the following code:

export default async function handler(req, res) {
  const response = await fetch("https://api.replicate.com/v1/predictions", {
    method: "POST",
    headers: {
      Authorization: `Token ${process.env.REPLICATE_API_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      // Pinned to a specific version of Stable Diffusion
      // See https://replicate.com/stability-ai/stable-diffussion/versions
      version: "6359a0cab3ca6e4d3320c33d79096161208e9024d174b2311e5a21b6c7e1131c",

      // This is the text prompt that will be submitted by a form on the frontend
      input: { prompt: req.body.prompt },
    }),
  });

  if (response.status !== 201) {
    let error = await response.json();
    res.statusCode = 500;
    res.end(JSON.stringify({ detail: error.detail }));
    return;
  }

  const prediction = await response.json();
  res.statusCode = 201;
  res.end(JSON.stringify(prediction));
}

Now create a file to handle requests to poll for the prediction's status. Call it pages/api/predictions/[id].js and add the following code:

export default async function handler(req, res) {
  const response = await fetch(
    "https://api.replicate.com/v1/predictions/" + req.query.id,
    {
      headers: {
        Authorization: `Token ${process.env.REPLICATE_API_TOKEN}`,
        "Content-Type": "application/json",
      },
    }
  );
  if (response.status !== 200) {
    let error = await response.json();
    res.statusCode = 500;
    res.end(JSON.stringify({ detail: error.detail }));
    return;
  }

  const prediction = await response.json();
  res.end(JSON.stringify(prediction));
}

Note the [id] in the filename. Next.js has a feature called dynamic routing that treats the id part of the URL as a variable. You can use this variable in your code by accessing req.query.id.

Step 5: Build the frontend

You've finished writing the server-side code that talks to the Replicate API. Now it's time to create the frontend code that renders a form. When a user enters a prompt and submits the form, it posts the data to the server-side endpoint that you created in Step 4 to create the prediction with the Replicate API.

Your project already has a file called pages/index.js that renders the default "Welcome to Next.js" home route. Remove all the existing content in that file and replace it with the following code:

import { useState } from "react";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

export default function Home() {
  const [prediction, setPrediction] = useState(null);
  const [error, setError] = useState(null);

  const handleSubmit = async (e) => {
    e.preventDefault();
    const response = await fetch("/api/predictions", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        prompt: e.target.prompt.value,
      }),
    });
    let prediction = await response.json();
    if (response.status !== 201) {
      setError(prediction.detail);
      return;
    }
    setPrediction(prediction);

    while (
      prediction.status !== "succeeded" &&
      prediction.status !== "failed"
    ) {
      await sleep(1000);
      const response = await fetch("/api/predictions/" + prediction.id);
      prediction = await response.json();
      if (response.status !== 200) {
        setError(prediction.detail);
        return;
      }
      console.log({prediction})
      setPrediction(prediction);
    }
  };

  return (
    <div className={styles.container}>
      <Head>
        <title>Replicate + Next.js</title>
      </Head>

      <p>
        Dream something with{" "}
        <a href="https://replicate.com/stability-ai/stable-diffusion">stability-ai/stable-diffusion</a>:
      </p>

      <form className={styles.form} onSubmit={handleSubmit}>
        <input type="text" name="prompt" placeholder="Enter a prompt to display an image" />
        <button type="submit">Go!</button>
      </form>

      {error && <div>{error}</div>}

      {prediction && (
        <div>
            {prediction.output && (
              <div className={styles.imageWrapper}>
              <Image
                fill
                src={prediction.output[prediction.output.length - 1]}
                alt="output"
                sizes='100vw'
              />
              </div>
            )}  
            <p>status: {prediction.status}</p>
        </div>
      )}
    </div>
  );
}

Step 6: Add basic styles

The Next.js starter app includes some CSS styles that are used on the default splash page, but they aren't really intended to be reused for a real app.

To create a clean slate for your styles, remove all the content in styles/globals.css and overwrite styles/Home.module.css with the following basic styles:

.container {
  padding: 2rem;
  font-size: 1.3rem;
  max-width: 48rem;
  margin: 0 auto;
}

.form {
  display: flex;
  margin-bottom: 2rem;
}

.form input {
  width: 100%;
  padding: 1rem;
  border: 1px solid #000;
  border-radius: 0.25rem;
  font-size: 1.3rem;
  margin-right: 1rem;
}

.form button {
  padding: 1rem;
  border: none;
  border-radius: 0.25rem;
  box-sizing: border-box;
  cursor: pointer;
  font-size: 1.3rem;
}

.imageWrapper {
  width: 100%;
  aspect-ratio: 1 / 1;
  position: relative
}

The [name].module.css naming pattern is a convention in Next.js for using CSS Modules, which scope CSS styles locally per filename to avoid collisions. See Component-Level CSS for more details on how this works.

Step 7: Configure image hosts

To protect your application from malicious users, Next.js requires some configuration to use external images. Edit the next.config.js file and add replicate.com and replicate.delivery to the images.domains array:

const nextConfig = {
  reactStrictMode: true,
  images: {
    remotePatterns: [
      {
        protocol: "https",
        hostname: "replicate.com",
      },
      {
        protocol: "https",
        hostname: "replicate.delivery",
      },
    ],
  },
};

Stop and restart your server for this configuration change to take effect.

Step 8: Create a prediction

Your app should be ready to use now! Visit localhost:3000 and enter a prompt to see the results.

renaissance painting of a figure giving two thumbs up

Step 9: Publish to GitHub

Now that your app is working, it's time to publish it to a GitHub repository. This step is not strictly necessary, but it's a good idea to keep your code in a version control system like Git. This will also set you up nicely to use Vercel's GitHub integration, which deploys your app automatically every time you push a new commit to the main branch on GitHub.

First, commit your changes to Git:

git add pages/api/predictions/
git commit -am "First working version! ๐ŸŽ‰"

Then create a new GitHub repository and push your code to it. You can use whatever flow you like, but here we'll go with the following command that uses GitHub's official gh CLI to create a new public repo named my-replicate-app and push your code to it:

gh repo create my-replicate-app --public --push --source=.

If you'd rather keep your repository private, set the --private flag instead of --public.

Step 10: Deploy to Vercel

There are many ways to deploy apps to Vercel, but for the sake of brevity, we'll use the vercel CLI here. Start by installing the CLI and running it:

npx vercel

The command above installs the CLI, then walks you through the process of logging in to Vercel, creating the app, and deploying it.

Once you've deployed your app, you need to add your Replicate API token to the remote app's environment variables. This allows your app to make requests to the Replicate API.

vercel env add REPLICATE_API_TOKEN

The command above prompts you to enter a value for your token. Paste the same token you used in Step 3. You then need to deploy again:

npx vercel deploy --prod

Next steps

You did it! You should now have a working web app that's powered by machine learning.

But this is just the start. Here are some ideas for what you can do next:

๐Ÿ˜Ž Show your friends what you've built.

๐Ÿš‚ Train and deploy your own DreamBooth model and use your new website to show it off.

๐Ÿ”Ž Integrate a super resolution model into your new app to upscale the generated images to a higher resolution.

๐Ÿค– Explore other models on Replicate and integrate them into your app.

โœ๏ธ Update the README if you're planning to open-source your project so others know how to use it and contribute.

โšก๏ธ Connect your Vercel app to your GitHub repo, so you'll get preview deployments for every pull request, and your app will automatically deploy every time you push to the main branch.