From Zero to Meme Hero: How I Built an AI-Powered Meme Generator in React
Learn how to build an AI meme generator using OpenAI, React, Fabric.js, and DALL-E 3 and create captions, design meme canvases, and optimize costs and performance.
Join the DZone community and get the full member experience.
Join For FreeWhy Would One Even Construct an AI Meme Generator?
Memes are literally the one thing on the internet that anyone can understand. Whether you want to take a jab at your friend or want to show how coding sometimes gives you brain freezes, memes will always come to your rescue. The issue? Manually doing everything takes ages. You need to source the right picture, come up with snarky lines, and then figure out how to stick everything together without making it look like something a 5-year-old put together.
But now, there are tools such as OpenAI and DeepSeek. With these, you don’t just automate comedy; you also automate currently trending formats and allow users to create memes in a matter of seconds. Here is how we approached our tasks:
- To generate engaging captions from memes, we created a context-specific approach.
- We built a super simple and straightforward drag-and-drop design interface.
- Finding new ways to economize API expenses allowed us to stay within the budget.
- Allowing users to store their most liked memes and adding a text-to-image meme feature made it possible.
Looking Back at My Favorite Tools
Before diving into the nitty-gritty details of the code, let’s discuss the tech stack a bit. And, just as a side note, assuming that you’ll be constructing a house without knowing what tools you’ll use is impractical.
- React + TypeScript. For the smooth, responsive UI React had brought into the world, TypeScript enabled the Team to catch many bugs that would have previously occurred.
- OpenAI/DeepSeek APIs. As long as there was a budget, the rest was history, with Division-04 being capable of delivering incisive, funny captions at will using GPT-4 Turbo. When they were limited, DeepSeek saved the day.
- Fabric.js. This library helps to have images with text dragged around easily, rather than feeling like one is trying to wrestle with a piglet drenched in oil.
- Vercel. Deployment utopia. It was also great during peak times because edge caching softened the blow.
- Redis. Low barrier to entry for protecting against rate limit and API abuse enforcement.
Step 1: Set Up Your Own AI Brain
What is clear is that an AI copying phrases from the internet will not work for memes like an AI telling you the response is “that’s hilarious” can. Memes require an amalgam of attitude, phrasing, and some level of restraint. This brings us to the more fundamental problem of how you tell an AI to make jokes. Tweaking the prompts of the AI itself, of course.
Here’s a snip of the code used to create the captions:
// src/services/aiService.ts
type MemePrompt = {
template: string; // e.g., "Distracted Soul"
context: string; // e.g., "When your code works on the first try"
};
const generateMemeCaption = async ({ template, context }: MemePrompt) => {
const prompt = `
Generate a sarcastic meme caption for the "${template}" template about "${context}".
Rules:
- Use Gen-Z slang (e.g., "rizz", "sigma")
- Max 12 words
- Add emojis related to the context
`;
const response = await openai.chat.completions.create({
model: "gpt-4-turbo",
messages: [{ role: "user", content: prompt }],
temperature: 0.9, // Higher = riskier jokes
max_tokens: 50,
});
return stripEmojis(response.choices[0].message.content); // No NSFW stuff allowed
};
Pro tip: For humor, keep it around 0.7 to 0.9, but make sure to always moderate the response through OpenAI’s moderation endpoint for safety reasons.
Step 2: Constructing the Meme Canvas
If you attempted to deal with the HTML5 Canvas APIs, you understand how not straightforward they are to deal with. Luckily, Fabric.js came to the savior. It gave us Photoshop-like controls directly inside React with the added bonus of drag-and-drop.
Take a look at this simplified version of our canvas component:
// src/components/MemeCanvas.tsx
import { FabricJSCanvas, useFabricJSEditor } from "fabricjs-react";
export default function MemeCanvas() {
const { editor, onReady } = useFabricJSEditor();
const [textColor, setTextColor] = useState("#FFFFFF");
const addTextLayer = (text: string) => {
editor?.addText(text, {
fill: textColor,
fontFamily: "Impact",
fontSize: 40,
stroke: "#000000",
strokeWidth: 2,
shadow: "rgba(0,0,0,0.5) 2px 2px 2px",
});
};
return (
<>
<button onClick={() => addTextLayer("Why React, why?!")}>Add Default Text</button>
<input type="color" onChange={(e) => setTextColor(e.target.value)} />
<FabricJSCanvas className="canvas" onReady={onReady} />
</>
);
}
Perks of this:
- Frees the text layers to be dragged anywhere on the document.
- Add color with stroke and shadow using the advanced color picker.
- Double-click to edit text to streamline the editing process.
Step 3: Rate Limitations
Imagine this for a moment: You release your app, and all of a sudden, everybody wants to make memes. Sounds fun, right? Until the OpenAI bill shoots up more than the price of Bitcoin.
To address this, we put in place sliding window rate limiting with Redis. This is how we did it on Vercel Edge Functions:
// src/app/api/generate-caption/route.ts
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(15, "86400s"), // 15 requests/day per IP
});
export async function POST(request: Request) {
const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1";
const { success } = await ratelimit.limit(ip);
if (!success) {
return new Response("Slow down, meme lord! Daily limit reached.", {
status: 429,
});
}
// Proceed with OpenAI call
}
Hack to Save Costs
- Cache popular prompts such as "Hotline Bling" and "When Pull Request gets approved."
- Use CloudFlare to cache generated images.
AI-Generated Meme Images From DALL-E 3
Sometimes, we learn the hard way that selecting the perfect meme template is an impossible task.
// src/services/aiService.ts
const generateCustomMemeImage = async (prompt: string) => {
const response = await openai.images.generate({
model: "dall-e-3",
prompt: `
A meme template about "${prompt}".
Style: Flat vector, bold outlines, no text.
Background: Solid pastel color.
`,
size: "1024x1024",
quality: "hd",
});
return response.data[0].url;
};
Changing the Output
- Prompt: "Two developers in a dispute over Redux and Zustand frameworks."
- Final product: An argument between Redux and Zustand cartoon characters is illustrated by two metabolizing icons fighting against a purple background.
Meme History Feature (Zustad + LocalStorage)
In order to enable users to keep collages, we added a meme history feature with the help of Zustand.
// src/stores/memeHistory.ts
import { create } from "zustand";
import { persist } from "zustand/middleware";
type Meme = {
id: string;
imageUrl: string;
caption: string;
timestamp: number;
};
interface MemeHistoryState {
memes: Meme[];
saveMeme: (meme: Omit<Meme, "id" | "timestamp">) => void;
}
export const useMemeHistory = create<MemeHistoryState>()(
persist(
(set, get) => ({
memes: [],
saveMeme: (meme) => {
const newMeme = {
...meme,
id: crypto.randomUUID(),
timestamp: Date.now(),
};
set({ memes: [newMeme, ...get().memes].slice(0, 100) });
},
}),
{ name: "meme-history" }
)
);
User Occupational Stream
- Let’s create a meme, and then we can click on save.
- The meme will be kept locally and will be presented in a grid format.
- The meme that has been saved can be reloaded in the editor by clicking on it.
Closing Thoughts
Building an AI meme generator helped deepen my understanding, not just of coding, but of how to handle certain unexpected scenarios. I learned the hard way the importance of preparation from implementing harsh limit rates to enduring Reddit traffic surges.
So, give it a try, work from the bottom up while making changes based on the feedback received, and enjoy yourself in the process. Perhaps your app might become popular, making you the next meme millionaire.
Opinions expressed by DZone contributors are their own.
Comments