Article

Β·

Generator unlocked: Create memes with ChatGPT and NextJS

Eric Allam

Eric Allam

CTO, Trigger.dev

Image for Generator unlocked: Create memes with ChatGPT and NextJS

TL;DR

In this tutorial, you'll learn how to build a meme generator in Next.js. We will be using:

  • Trigger.dev for handling the meme's creation in the background
  • Supabase for storing and saving the memes
  • Resend for sending the generated meme to the user

Your background job management for NextJS

Trigger.dev is an open-source library that enables you to create and monitor long-running jobs for your app with NextJS, Remix, Astro, and so many more!

If you can spend 10 seconds giving us a star, I would be super grateful πŸ’– https://github.com/triggerdotdev/trigger.dev

Star our repo

Let's set it up πŸ”₯

Here, I'll walk you through creating the user interface for the meme generator application.

Create a new Next.js project by running the code snippet below.


_10
npx create-next-app memes-generator

First, you need to create a homepage displaying a form that enables users to enter the meme audience and topic. You can also add another field for the user's email address to receive the meme once it is ready. Below the form, users should be able to view the recently generated memes, as shown below.

Meme Magic

When the user submits the form, they will be redirected to a submission page while the application generates the meme in the background and sends it to the user's email.

Meme Magic thank you

Home page 🏠

Before we proceed, run the code snippet below to install the React Tag Input package. It enables us to provide multiple inputs for a value.


_10
npm install react-tag-input

Copy the code snippet below into the index.js file.


_89
"use client";
_89
import { Space_Grotesk } from "next/font/google";
_89
import { useState } from "react";
_89
import { WithContext as ReactTags } from "react-tag-input";
_89
import { useRouter } from "next/navigation";
_89
import ViewMemes from "@/components/ViewMemes";
_89
_89
const KeyCodes = {
_89
comma: 188,
_89
enter: 13,
_89
};
_89
const inter = Space_Grotesk({ subsets: ["latin"] });
_89
const delimiters = [KeyCodes.comma, KeyCodes.enter];
_89
_89
export default function Home() {
_89
const [audience, setAudience] = useState("");
_89
const [email, setEmail] = useState("");
_89
const [memes, setMemes] = useState([]);
_89
const router = useRouter();
_89
const [topics, setTopics] = useState([
_89
{ id: "Developers", text: "Developers" },
_89
]);
_89
_89
const handleDelete = (i) =>
_89
setTopics(topics.filter((topic, index) => index !== i));
_89
_89
const handleAddition = (topic) => setTopics([...topics, topic]);
_89
_89
const handleSubmit = (e) => {
_89
e.preventDefault();
_89
console.log({
_89
audience,
_89
topics,
_89
email,
_89
});
_89
router.push("/submit");
_89
};
_89
_89
return (
_89
<main className={`min-h-screen w-full ${inter.className}`}>
_89
<header className="flex min-h-[95vh] flex-col items-center justify-center bg-[#F8F0E5]">
_89
<h2 className="mb-2 text-4xl font-bold text-[#0F2C59]">Meme Magic</h2>
_89
<h3 className="mb-8 text-lg opacity-60">
_89
Creating memes with a touch of magic
_89
</h3>
_89
<form
_89
className="flex w-[95%] flex-col md:w-[70%]"
_89
onSubmit={handleSubmit}
_89
>
_89
<label htmlFor="audience">Audience</label>
_89
<input
_89
type="text"
_89
name="audience"
_89
value={audience}
_89
required
_89
className="mb-4 rounded px-4 py-2"
_89
onChange={(e) => setAudience(e.target.value)}
_89
/>
_89
<label htmlFor="email">Your email address</label>
_89
<input
_89
type="email"
_89
name="email"
_89
value={email}
_89
required
_89
className="mb-4 rounded px-4 py-2"
_89
onChange={(e) => setEmail(e.target.value)}
_89
/>
_89
_89
<ReactTags
_89
tags={topics}
_89
delimiters={delimiters}
_89
handleDelete={handleDelete}
_89
handleAddition={handleAddition}
_89
inputFieldPosition="top"
_89
autocomplete
_89
placeholder="Enter a topic for the meme and press enter"
_89
/>
_89
<button
_89
type="submit"
_89
className="mt-[30px] rounded bg-[#0F2C59] py-4 text-lg font-bold text-[#fff] hover:bg-[#151d2b]"
_89
>
_89
GENERATE MEME
_89
</button>
_89
</form>
_89
</header>
_89
<ViewMemes memes={memes} />
_89
</main>
_89
);
_89
}

The code snippet creates input fields that enable users to enter the email, audience, and topic of the meme to be generated. The <ReactTags> component allows us to enter multiple values as the meme topic.

Meme Magic 2

Next, create a components folder containing the ViewMemes.jsx file, where all the recently generated memes are displayed.


_28
import React from "react";
_28
import Image from "next/image";
_28
_28
const ViewMemes = ({ memes }) => {
_28
return (
_28
<section className="flex h-[100vh] flex-col items-center px-4 py-10">
_28
<h2 className="mb-8 text-3xl font-bold">Recent Memes</h2>
_28
<div className="flex w-full flex-wrap md:space-x-4">
_28
{memes.map((meme) => (
_28
<div
_28
className="m-2 flex w-full flex-col items-center md:w-[30%]"
_28
key={meme.id}
_28
>
_28
<Image
_28
src={`${meme.meme_url}`}
_28
alt={meme.name}
_28
className="rounded hover:scale-105"
_28
width={400}
_28
height={400}
_28
/>
_28
</div>
_28
))}
_28
</div>
_28
</section>
_28
);
_28
};
_28
_28
export default ViewMemes;

Meme submission 🐀

Create a submit.js file that displays a "Thank You" message to the user after submitting the form.


_18
import Link from "next/link";
_18
import React from "react";
_18
_18
export default function Submit() {
_18
return (
_18
<div className="flex h-screen w-full flex-col items-center justify-center">
_18
<h2 className="mb-4 text-4xl font-extrabold text-[#DAC0A3]">
_18
Thank You!
_18
</h2>
_18
<p className="mb-6">
_18
Your newly generated meme has been sent to your email.
_18
</p>
_18
<Link href="/">
_18
<p className="text-[#662549]">Go Home</p>
_18
</Link>
_18
</div>
_18
);
_18
}

Generate memes in the background πŸ₯

In this section, I will walk you through adding Trigger.dev to a Next.js project. First, you need to visit the Trigger.dev homepage and create a new account.

Next, create an organization and project name for your jobs.

Create a new organization on Trigger.dev

Lastly, follow the steps shown to you.

Setup Trigger.dev with next.js

If this is not your initial Trigger.dev project, follow the steps below. Click Environments & API Keys on the sidebar menu of your project dashboard.

Setup environments and api keys on Trigger.dev

Copy your DEV server API key and run the code snippet below to install Trigger.dev. Follow the instructions carefully.


_10
npx @trigger.dev/cli@latest init

Start your Next.js project.


_10
npm run dev

Also, in another terminal, run the code snippet below to create a tunnel between the Trigger.dev and your Next.js project.


_10
npx @trigger.dev/cli@latest dev

Finally, rename the jobs/examples.js file to jobs/functions.js. This is where all the jobs are processed.

Congratulations!πŸŽ‰ You've successfully added Trigger.dev to your Next.js app.


How to generate meme templates and captions with OpenAI and ImgFlip

In this section, you'll learn how to fetch meme templates from ImgFlip and generate meme captions from OpenAI via Trigger.dev.

Before we proceed, update the index.js file to send the user's input to the server.


_34
//πŸ‘‡πŸ» runs when a user submits the form
_34
const handleSubmit = (e) => {
_34
e.preventDefault();
_34
if (topics.length > 0) {
_34
let topic = "";
_34
topics.forEach((tag) => {
_34
topic += tag.text + ", ";
_34
});
_34
//πŸ‘‡πŸ» sends the data to the backend
_34
postData(topic);
_34
router.push("/submit");
_34
}
_34
};
_34
_34
//πŸ‘‡πŸ» sends the data to the backend server
_34
const postData = async (topic) => {
_34
try {
_34
const data = await fetch("/api/api", {
_34
method: "POST",
_34
body: JSON.stringify({
_34
audience,
_34
topic,
_34
email,
_34
}),
_34
headers: {
_34
"Content-Type": "application/json",
_34
},
_34
});
_34
const response = await data.json();
_34
console.log(response);
_34
} catch (err) {
_34
console.error(err);
_34
}
_34
};

The postData function sends the user's input to the /api/api endpoint on the server when the user submits the form.

Next, create an api.js file within the api folder that accepts the user's data.


_10
export default function handler(req, res) {
_10
const { topic, audience, email } = req.body;
_10
_10
if (topic && audience && email) {
_10
console.log({ audience, topic, email });
_10
}
_10
res.status(200).json({ message: "Successful" });
_10
}

Meme background job πŸ‘¨πŸ»β€πŸ”§

There are three ways of communicating via Trigger which are webhook, schedule, and event. Webhook triggers a job in real-time when specific events occur, Schedule works for repeating tasks, and Events trigger a job when you send a payload.

Here, I'll guide you through using Event triggers to execute jobs in the application.

Within the jobs/functions.js file, update the client.defineJob function as done below.


_36
client.defineJob({
_36
id: "generate-meme",
_36
name: "Generate Meme",
_36
version: "0.0.1",
_36
trigger: eventTrigger({
_36
name: "generate.meme",
_36
}),
_36
run: async (payload, io, ctx) => {
_36
const { audience, topic, email } = payload;
_36
// This logs a message to the console and adds an entry to the run dashboard
_36
await io.logger.info("Meme request received!βœ…");
_36
await io.logger.info("Meme generation in progress 🀝");
_36
_36
// Wrap your code in io.runTask to get automatic error handling and logging
_36
const selectedTemplate = await io.runTask("fetch-meme", async () => {
_36
const fetchAllMeme = await fetch("https://api.imgflip.com/get_memes");
_36
const memesData = await fetchAllMeme.json();
_36
const memes = memesData.data.memes;
_36
_36
const randInt = Math.floor(Math.random() * 101);
_36
_36
return memes[randInt];
_36
});
_36
_36
const userPrompt = `Topics: ${topic} \n Intended Audience: ${audience} \n Template: ${selectedTemplate.name} \n`;
_36
_36
const sysPrompt = `You are a meme idea generator. You will use the imgflip api to generate a meme based on an idea you suggest. Given a random template name and topics, generate a meme idea for the intended audience. Only use the template provided.`;
_36
_36
await io.logger.info("✨ Yay! You've gotten a template for the meme ✨", {
_36
userPrompt,
_36
sysPrompt,
_36
selectedTemplate,
_36
email,
_36
});
_36
},
_36
});

The code snippet above creates a new job that fetches a random meme template from ImgFlip, creates a user, and a system prompt for OpenAI to generate a meme caption based on the data provided by the user.

To trigger this job, update the /api/api.js file to send the user's data as a payload to the job.


_22
import { client } from "@/trigger";
_22
_22
export default function handler(req, res) {
_22
const { topic, audience, email } = req.body;
_22
try {
_22
async function fetchMeme() {
_22
if (topic && audience && email) {
_22
await client.sendEvent({
_22
name: "generate.meme",
_22
payload: {
_22
audience,
_22
topic,
_22
email,
_22
},
_22
});
_22
}
_22
}
_22
fetchMeme();
_22
} catch (err) {
_22
return res.status(400).json({ message: err });
_22
}
_22
}

The code snippet above triggers the job via its name - generate.meme and sends the audience, topic, and the user's email as a payload to the job.

Once this is successful, you should see the status of the job on your Trigger.dev dashboard and monitor its activity.

Trigger.dev app Job list

Trigger.dev also allows us to nest jobs within jobs; that is, you can trigger another job within a job. This makes it possible for you to trigger a job that will execute all the nested jobs within itself.

In the previous job, we logged the data retrieved to the console; Next, let's trigger another job within the job.


_40
client.defineJob({
_40
id: "generate-meme",
_40
name: "Generate Meme",
_40
version: "0.0.1",
_40
trigger: eventTrigger({
_40
name: "generate.meme",
_40
}),
_40
run: async (payload, io, ctx) => {
_40
const { audience, topic, email } = payload;
_40
// This logs a message to the console and adds an entry to the run dashboard
_40
await io.logger.info("Meme request received!βœ…");
_40
await io.logger.info("Meme generation in progress 🀝");
_40
_40
// Wrap your code in io.runTask to get automatic error handling and logging
_40
const selectedTemplate = await io.runTask("fetch-meme", async () => {
_40
const fetchAllMeme = await fetch("https://api.imgflip.com/get_memes");
_40
const memesData = await fetchAllMeme.json();
_40
const memes = memesData.data.memes;
_40
_40
const randInt = Math.floor(Math.random() * 101);
_40
_40
return memes[randInt];
_40
});
_40
_40
const userPrompt = `Topics: ${topic} \n Intended Audience: ${audience} \n Template: ${selectedTemplate.name} \n`;
_40
_40
const sysPrompt = `You are a meme idea generator. You will use the imgflip api to generate a meme based on an idea you suggest. Given a random template name and topics, generate a meme idea for the intended audience. Only use the template provided.`;
_40
_40
// Trigger any job listening for the gpt.text event
_40
await io.sendEvent("generate-gpt-text", {
_40
name: "gpt.text",
_40
payload: {
_40
userPrompt,
_40
sysPrompt,
_40
selectedTemplate,
_40
email,
_40
},
_40
});
_40
},
_40
});

The code snippet above sends the data retrieved from the job as a payload to an event - where you can generate the meme caption. Each event has a unique name, and any job that is triggered by an event with a matching name will be run. You can send events using the io.sendEvent method.

Now that we are selecting a template and generate prompts, we can implement the job for generating meme text by listening for the gpt.text event.


Generate the meme text with ChatGPT πŸ€–

Here, you'll learn how to communicate with OpenAI using the Trigger.dev OpenAI integration. We'll use function calls for generating captions for the meme templates. Before we proceed, create an OpenAI account and generate an API Key.

OpenAI dashboard

Click View API key from the dropdown to create an API Key.

OpenAI dashboard

Next, install the OpenAI package by running the code snippet below.


_10
npm install @trigger.dev/openai

Add your OpenAI API key to the .env.local file.


_10
OPENAI_API_KEY=<your_api_key>

Import the OpenAI package into the jobs/functions.js file.


_10
import { OpenAI } from "@trigger.dev/openai";
_10
_10
const openai = new OpenAI({ id: "openai", apiKey: process.env.OPENAI_API_KEY });

Finally, create the job that generates the meme captions via OpenAI function call.


_59
client.defineJob({
_59
id: "chatgpt-meme-text",
_59
name: "ChatGPT Meme Text",
_59
version: "0.0.1",
_59
trigger: eventTrigger({
_59
name: "gpt.text",
_59
}),
_59
run: async (payload, io, ctx) => {
_59
const { userPrompt, sysPrompt, selectedTemplate, email } = payload;
_59
await io.logger.info("✨ Talking to ChatGPT πŸ«‚");
_59
const messages = [
_59
{ role: "system", content: sysPrompt },
_59
{ role: "user", content: userPrompt },
_59
];
_59
const functions = [
_59
{
_59
name: "generateMemeImage",
_59
description:
_59
"Generate meme via the imgflip API based on the given idea",
_59
parameters: {
_59
type: "object",
_59
properties: {
_59
text0: {
_59
type: "string",
_59
description: "The text for the top caption of the meme",
_59
},
_59
text1: {
_59
type: "string",
_59
description: "The text for the bottom caption of the meme",
_59
},
_59
},
_59
required: ["templateName", "text0", "text1"],
_59
},
_59
},
_59
];
_59
const response = await openai.chat.completions.create("create-meme", {
_59
model: "gpt-3.5-turbo",
_59
messages,
_59
functions,
_59
function_call: "auto",
_59
});
_59
_59
const responseMessage = response.choices[0];
_59
const texts = extractSentencesInQuotes(responseMessage.message.content);
_59
_59
await io.logger.info("✨ Yay! You've gotten a text for your meme ✨", {
_59
texts,
_59
});
_59
_59
await io.sendEvent("caption-save-meme", {
_59
name: "caption.save.meme",
_59
payload: {
_59
texts: ["Text0", "Text1"],
_59
selectedTemplate,
_59
email,
_59
},
_59
});
_59
},
_59
});

The code snippet above generates a caption for the meme via the OpenAI function calling feature, extracts the caption, and passes it as a payload to another job. The payload contains the caption, selected meme template, and the user's email.

The extractSentencesInQuotes function retrieves the caption from the data returned by OpenAI.


_10
const extractSentencesInQuotes = (inputString) => {
_10
const sentenceRegex = /"([^"]+)"/g;
_10
const sentences = [];
_10
let match;
_10
while ((match = sentenceRegex.exec(inputString))) {
_10
sentences.push(match[1]);
_10
}
_10
return sentences;
_10
};

So far, we've been able to select a meme template from ImgFlip and generate the meme caption via OpenAI. In the upcoming sections, you'll learn how to add the caption to the meme templates.

Set the memes title πŸ“

ImgFlip API provides a /caption_image endpoint that accepts required parameters, such as template ID, username, password, and the captions - text0 and text1. Text0 is the Top text for the meme, and Text1 is the Bottom text for the meme.

Meme example text placement

Create an account on ImgFlip, and save your username and password into the .env.local file.


_10
IMGFLIP_USERNAME=<your_username>
_10
IMGFLIP_PW=<your_password>

Add a new job to the jobs/functions.js file that adds a caption to the meme and saves it to Supabase.


_36
client.defineJob({
_36
id: "caption-save-meme",
_36
name: "Caption and Save Meme",
_36
version: "0.0.1",
_36
trigger: eventTrigger({
_36
name: "caption.save.meme",
_36
}),
_36
run: async (payload, io, ctx) => {
_36
const { texts, selectedTemplate, email } = payload;
_36
_36
await io.logger.info("Received meme template and texts πŸŽ‰");
_36
_36
const formatData = new URLSearchParams({
_36
template_id: selectedTemplate.id,
_36
username: process.env.IMGFLIP_USERNAME,
_36
password: process.env.IMGFLIP_PW,
_36
text0: texts[0],
_36
text1: texts[1],
_36
});
_36
_36
const captionedMeme = await io.runTask("caption-meme", async () => {
_36
const response = await fetch("https://api.imgflip.com/caption_image", {
_36
method: "POST",
_36
body: formatData.toString(),
_36
headers: {
_36
"Content-Type": "application/x-www-form-urlencoded",
_36
},
_36
});
_36
return await response.json();
_36
});
_36
_36
await io.logger.info("✨ Yay! Your meme has been captioned! ✨", {
_36
captionedMeme,
_36
});
_36
},
_36
});

The code snippet above accepts the required parameters and sends them to the endpoint provided by ImgFlip. The endpoint receives the meme ID, caption text, and user's credentials in a URL-encoded format.

In the upcoming section, you'll learn how to save the captioned meme to Supabase.


Save the memes and display them πŸ’Ύ

Supabase is an open-source Firebase alternative that enables you to add authentication, file storage, Postgres, and real-time database to your software applications. With Supabase, you can build secured and scalable applications in a few minutes.

In this section, I'll guide you through setting up and interacting with a Supabase backend from a Next.js app.

Visit the Supabase homepage and create a new organization and project.

Supabase new project page

Next, create a new table containing the following columns, as shown below, and add some dummy data to the database table.

Create a new table in Supabase

The table stores the id, name, and meme_url properties received from the application. The created_at is auto-generated and saves the data timestamp.

Run the code snippet below to install the Supabase package into Next.js.


_10
npm install @supabase/supabase-js

Click API on the sidebar menu and copy the project's URL and API into the .env.local file.


_10
NEXT_PUBLIC_SUPABASE_URL=<public_supabase_URL>
_10
NEXT_PUBLIC_SUPABASE_ANON_KEY=<supabase_anon_key>

Supabase API settings page

Finally, create a src/supbaseClient.js file and create the Supabase client for the application.


_10
import { createClient } from "@supabase/supabase-js";
_10
_10
const supabaseURL = process.env.NEXT_PUBLIC_SUPABASE_URL;
_10
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
_10
_10
export const supabase = createClient(supabaseURL, supabaseAnonKey, {
_10
auth: { persistSession: false },
_10
});

Congratulations!πŸŽ‰ You've successfully added Supabase to the Next.js application.

Fetching all the available memes

Create an api/all-memes endpoint on the Next.js server that retrieves all the memes from Supabase.


_16
import { supabase } from "@/supabaseClient";
_16
_16
export default function handler(req, res) {
_16
const fetchMemes = async () => {
_16
try {
_16
const { data, error } = await supabase
_16
.from("memes")
_16
.select("*")
_16
.order("created_at", { ascending: false });
_16
res.status(200).json({ data });
_16
} catch (err) {
_16
res.status(400).json({ error: err });
_16
}
_16
};
_16
fetchMemes();
_16
}

Then, send a request to the endpoint when a user visits the application's homepage.


_13
useEffect(() => {
_13
fetchMemes();
_13
}, []);
_13
_13
const fetchMemes = async () => {
_13
try {
_13
const request = await fetch("/api/all-memes");
_13
const response = await request.json();
_13
setMemes(response.data);
_13
} catch (err) {
_13
console.error(err);
_13
}
_13
};

Saving the memes to Supabase

Import Supabase from the @/supabaseClient file into the @/jobs/functions.js file.


_10
import { supabase } from "@/supabaseClient";

Update the recently added job to save the meme to Supabase.


_54
client.defineJob({
_54
id: "caption-save-meme",
_54
name: "Caption and Save Meme",
_54
version: "0.0.1",
_54
trigger: eventTrigger({
_54
name: "caption.save.meme",
_54
}),
_54
run: async (payload, io, ctx) => {
_54
const { texts, selectedTemplate, email } = payload;
_54
_54
await io.logger.info("Received meme template and texts πŸŽ‰");
_54
_54
const formatData = new URLSearchParams({
_54
template_id: selectedTemplate.id,
_54
username: process.env.IMGFLIP_USERNAME,
_54
password: process.env.IMGFLIP_PW,
_54
text0: texts[0],
_54
text1: texts[1],
_54
});
_54
_54
const captionedMeme = await io.runTask("caption-meme", async () => {
_54
const response = await fetch("https://api.imgflip.com/caption_image", {
_54
method: "POST",
_54
body: formatData.toString(),
_54
headers: {
_54
"Content-Type": "application/x-www-form-urlencoded",
_54
},
_54
});
_54
return await response.json();
_54
});
_54
_54
await io.logger.info("✨ Yay! Your meme has been captioned! ✨", {
_54
captionedMeme,
_54
});
_54
_54
await supabase.from("memes").insert({
_54
id: selectedTemplate.id,
_54
name: selectedTemplate.name,
_54
meme_url: captionedMeme.data.url,
_54
});
_54
_54
await io.sendEvent("email-meme", {
_54
name: "send.meme",
_54
payload: {
_54
email,
_54
meme_url: `http://localhost:3000/memes/${selectedTemplate.id}`,
_54
},
_54
});
_54
_54
await io.logger.info(
_54
"✨ Yay! Your meme has been saved to the database! ✨"
_54
);
_54
},
_54
});

The code snippet above adds a caption to the meme template and saves the newly created meme to Supabase. It also triggers another job, which sends the meme page URL to the user's email. You'll learn how to do this in the upcoming section.


Send email on completion 🎬

Resend is an email API that enables you to send texts, attachments, and email templates easily. With Resend, you can build, test, and deliver transactional emails at scale.

Here, you'll learn how to send emails via Resend. Go to the Signup page and create an account.

Create an API Key and save it into a .env.local file within your Next.js project.


_10
RESEND_API_KEY=<place_your_API_key>

Generate an API Key on Resend

Install the Trigger.dev Resend integration:


_10
npm i @trigger.dev/resend

Import Resend into the @/jobs/functions.js file as shown below.


_10
import { Resend } from "@trigger.dev/resend";
_10
const resend = new Resend({ id: "resend", apiKey: process.env.RESEND_API_KEY });

Add a final job that receives the meme URL and user's email from the previous job and sends the newly generated meme to the user upon completion.


_22
client.defineJob({
_22
id: "send-meme",
_22
name: "Send Meme",
_22
version: "0.0.1",
_22
trigger: eventTrigger({
_22
name: "send.meme",
_22
}),
_22
run: async (payload, io, ctx) => {
_22
const { meme_url, email } = payload;
_22
_22
await io.logger.info("Sending meme to the user πŸŽ‰");
_22
_22
await resend.sendEmail("πŸ“§", {
_22
_22
to: [email],
_22
subject: "Your meme is ready!",
_22
text: `Hey there, Your meme is ready.\n Access it here: ${meme_url}`,
_22
});
_22
_22
await io.logger.info("✨ Yay! Your meme has been emailed to the user! ✨");
_22
},
_22
});

The user receives an email containing a link in this format: http://localhost:3000/memes/${meme.id}. This link redirects the user to a specific page in the application that displays the meme. We will create this page in the upcoming section.

Displaying the newly generated meme

Create a memes/[id].js file that accepts the meme ID from the URL, get the meme from Supabase via its ID, and displays the meme on the page.

Displaying your generated meme

Copy the code snippet below into the memes/[id].js file.


_47
"use client";
_47
import React, { useEffect, useState } from "react";
_47
import Image from "next/image";
_47
import { usePathname } from "next/navigation";
_47
import Link from "next/link";
_47
_47
export default function Meme() {
_47
const params = usePathname();
_47
const [meme, setMeme] = useState({});
_47
_47
useEffect(() => {
_47
const fetchMeme = async () => {
_47
if (params) {
_47
const id = params.split("/")[2];
_47
const request = await fetch("/api/meme", {
_47
method: "POST",
_47
body: JSON.stringify({ id }),
_47
headers: {
_47
"Content-Type": "application/json",
_47
},
_47
});
_47
const response = await request.json();
_47
setMeme(response.data[0]);
_47
}
_47
};
_47
_47
fetchMeme();
_47
}, [params]);
_47
_47
return (
_47
<div className="flex min-h-screen w-full flex-col items-center justify-center">
_47
{meme?.meme_url && (
_47
<Image
_47
src={meme.meme_url}
_47
alt={meme.name}
_47
className="rounded hover:scale-105"
_47
width={500}
_47
height={500}
_47
/>
_47
)}
_47
_47
<Link href="/" className="mt-6 text-blue-500">
_47
Go back home
_47
</Link>
_47
</div>
_47
);
_47
}

The code snippet above retrieves the meme ID from the URL, sends it to the server via the /api/meme endpoint, and receives the meme details as a response.

Next, create the /api/meme endpoint.


_17
import { supabase } from "@/supabaseClient";
_17
_17
export default function handler(req, res) {
_17
const getMeme = async () => {
_17
const { id } = req.body;
_17
try {
_17
const { data, error } = await supabase
_17
.from("memes")
_17
.select("*")
_17
.eq("id", parseInt(id));
_17
res.status(200).json({ data });
_17
} catch (err) {
_17
res.status(400).json({ error: err });
_17
}
_17
};
_17
getMeme();
_17
}

The code snippet above fetches the data whose property matches the ID from Supabase.

Congratulations! You've completed the project.


Conclusion

So far, you've learnt how to create jobs within your codebase with Trigger, generate texts from OpenAI, save and retrieve data from Supabase, and send emails via Resend.

The source code for this tutorial is available here: https://github.com/triggerdotdev/blog/tree/main/memes-generator

Thank you for reading!

If you can spend 10 seconds giving us a star, I would be super grateful. πŸ’– https://github.com/triggerdotdev/trigger.dev

Star our repo

Ready to start building?

Build and deploy your first task in 3 minutes.

Get started now
,