Skip to content

RESOURCES / BLOG

Cloudinary Image Generation: Build an AI Image-Guessing Game in Next.js

Why use one image model when a single Cloudinary API can give your application all five current families? Flux, Recraft, GPT Image, Nano Banana, and Ideogram have different creative strengths. Exposing them through the unified Cloudinary Image Generation API lets a product choose the right model for each job without building separate authentication, storage, metadata, transformation, and delivery pipelines for every provider.

Pixel Pursuit turns that multi-model advantage into a Next.js image-guessing game. Cloudinary generates the hidden image, stores it as a managed asset, transforms one original into progressive clues, and prepares it for optimized delivery with optional Enterprise multi-CDN routing. The result is a reusable, searchable media asset that can be remixed, inspected, transformed, and delivered from the same Cloudinary product environment.

This project demonstrates standard and premium tiers across all five families, image-to-image remixing, metadata readback, a gallery, and delivery verification. Players can compare model interpretations while developers keep one API and asset workflow. The live demo limits each browser to two generation calls, then helps visitors clone the repository and connect their own Cloudinary account.

A player opens the game and reads a short first-visit guide before spending a generation. They choose a model family and tier, then the server selects a mystery prompt and calls Cloudinary’s text-to-image endpoint. The answer is never sent to the browser as plain JSON. It’s protected inside an encrypted, authenticated round token and checked by a server route.

Each incorrect guess advances the same asset through four clues:

  1. A deep blur hides nearly every detail.
  2. A pixelated version exposes color and shape.
  3. An automatic-gravity crop focuses on the likely subject.
  4. The complete image reveals the answer.

Once the round is solved or revealed, the player can inspect Cloudinary provenance and use the original asset as an authenticated reference for a new image-to-image generation.

flowchart LR
  B[Browser] -->|family and tier| N[Next.js server routes]
  N -->|Basic Auth| G[Cloudinary Image Generation API]
  G --> A[Managed original and metadata]
  A --> T[URL transformations]
  T --> C[Cloudinary multi-CDN delivery]
  C --> B
  N -->|asset ID| M[Cloudinary Admin API]
  M --> P[Proof panel and generation vault]

This architecture separates three trust zones. The browser owns presentation and player input. The Next.js server owns credentials, prompts, answer validation, quota decisions, and authenticated readback. Cloudinary owns managed originals, generation metadata, derived assets, and delivery.

You’ll need:

  • Node.js 20.9 or newer.
  • A free Cloudinary account.
  • The Cloudinary Image Generation add-on.
  • Familiarity with React and Next.js App Router route handlers.

The project uses Next.js 16, React 19, TypeScript, Tailwind CSS, shadcn/ui, next-cloudinary, and the Cloudinary Node.js SDK.

The Image Generation API is an evolving API, so check the Image Generation add-on documentation and API reference for the latest models, limits, and request fields before shipping.

Open the Image Generation add-on in the Cloudinary Console and register for it. For a new account, making the first generation in the Console activates the available plan. The official add-on guide documents registration, Console generation, supported models, and current quota behavior.

Next, open Settings > API Keys and copy your cloud name, API key, and API secret. The project sends Image Generation API requests from a Node.js route with HTTP Basic authentication, as described in the Image Generation authentication documentation. The secret must remain on the server.

Clone and configure the project:

git clone https://github.com/musebe/nextjs-cloudinary-ai-guessing-game.git
cd nextjs-cloudinary-ai-guessing-game
npm install
cp .env.example .env.local
Code language: PHP (php)

Add your values to .env.local:

NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
CLOUDINARY_IMAGE_GENERATION_FOLDER=image-guessing-game

The cloud name is public because it appears in delivery URLs. The API key and secret are server-only. Do not give the secret a NEXT_PUBLIC_ prefix.

The app can use CLOUDINARY_API_SECRET to sign its demo usage cookie. To separate those concerns, generate another high-entropy value:

openssl rand -base64 32
DEMO_USAGE_SECRET=replace_with_the_generated_value

The API returns a managed asset. Without a preset, the code carries the folder in target.public_id. A signed preset centralizes the folder and custom tag rather than repeating storage rules. Cloudinary’s upload preset guide explains preset security, folder modes, precedence, and programmatic management.

Create the preset in the Console:

  1. Select the Settings gear in the Cloudinary Console.
  2. Open Upload, then select the Upload Presets tab.
  3. Click Add Upload Preset.
  4. Enter pixel-pursuit-generations for Upload preset name.
  5. Set Signing mode to Signed. The Image Generation request comes from an authenticated Next.js server, so the browser does not need an unsigned preset name.
  6. In the storage section, set Asset folder to image-guessing-game.
  7. In the asset metadata section, add pixel-pursuit to Tags.
  8. Leave incoming and eager transformations empty. This game applies blur, pixelation, cropping, format, and quality at delivery time so it can create several clues from the unchanged original.
  9. Click Save.

The resulting configuration should match:

Console Field Value
Upload preset name pixel-pursuit-generations
Signing mode Signed
Asset folder image-guessing-game
Tags pixel-pursuit

This article assumes dynamic folder mode, where Asset folder controls the asset’s Media Library location without changing its public ID path. If your product environment uses legacy fixed folder mode, the Console shows different folder options. Follow the folder mode documentation and keep the destination consistent with CLOUDINARY_IMAGE_GENERATION_FOLDER.

Then update .env.local:

CLOUDINARY_IMAGE_GENERATION_PRESET=pixel-pursuit-generations

Restart npm run dev after changing the environment. Generate one round, open Assets > Media Library, and inspect the image-guessing-game folder. The asset should include the custom pixel-pursuit tag plus Cloudinary’s automatic text-to-image tag. An image-to-image remix receives image-to-image instead. The asset context should also expose model_id, prompt, and seed, matching the automatic tags and metadata documented for Image Generation.

Do not make this the default preset for the entire product environment. The application sends upload_preset: "pixel-pursuit-generations" explicitly in the generation target, which limits the preset’s effect to this workflow.

At the time of publication, the project exposes the complete supported Image Generation model catalog:

Family Standard Model Premium Model Seed Support
Flux flux-2-klein-9b flux-2-pro Yes
Recraft recraft-v3 recraft-v4 No
GPT Image gpt-image-1-mini gpt-image-2 No
Nano Banana nano-banana-1 nano-banana-2 Yes
Ideogram ideogram-v4-turbo ideogram-v4-base Yes

The UI presents each family with a purpose rather than only a model ID. Flux emphasizes photorealistic scenes, Recraft targets vector art and illustration, GPT Image suits campaign-style creative work, Nano Banana is a general-purpose option, and Ideogram supports text, realism, and art. Model Mix rotates through the catalog so the game can compare families across rounds.

Seed support matters when you want reproducible or closely related output. The server generates a random seed only for model families that accept one. This avoids sending unsupported request fields while preserving the returned seed in the result and Cloudinary context. Recheck the official model table before publishing because the API is evolving.

The core request lives in src/lib/cloudinary/image-generation.ts. It follows the Image Generation API reference, calls the account-scoped v2 endpoint, asks for a square 1K image, and persists the response as a managed asset.

const response = await fetch(
  `https://api.cloudinary.com/v2/generate/${cloudName}/text_to_image`,
  {
    method: "POST",
    headers: {
      Authorization: `Basic ${authorization}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      image_size: { aspect_ratio: "1:1", resolution: "1K" },
      model: { id: modelId },
      prompt,
      ...(seed === null ? {} : { seed }),
      target: {
        public_id: publicId,
        target_type: "managed_asset",
        ...(uploadPreset ? { upload_preset: uploadPreset } : {}),
      },
    }),
  },
);
Code language: JavaScript (javascript)

The full implementation adds a 120-second abort signal, structured error handling, request IDs, and add-on quota parsing. A UUID in the public ID prevents collisions. A managed_asset target can include both public_id and upload_preset, as shown in Cloudinary’s managed target example. The API response provides both asset_id and public_id, and the app keeps both because they solve different problems:

  • public_id is a stable input for Cloudinary delivery URLs and CldImage.
  • asset_id is an immutable identity for Admin API readback and verified references.

The POST /api/generate route validates the requested family and tier, checks the demo allowance, selects a server-owned prompt, calls the helper, and returns the managed asset plus a round token.

If the generation route returned the answer with the public ID, a player could open DevTools and bypass the game. Pixel Pursuit encrypts the answer, accepted aliases, asset ID, public ID, and expiration time using AES-256-GCM.

The token uses a random 12-byte initialization vector, additional authenticated data, and a key derived from the server-only Cloudinary secret. It expires after two hours. The browser can carry it between requests but cannot read or modify it without invalidating authentication.

The POST /api/guess route decrypts the token and normalizes both the guess and accepted answers:

function normalizeGuess(value: string): string {
  return value
    .normalize("NFKD")
    .replace(/\p{Diacritic}/gu, "")
    .toLocaleLowerCase("en")
    .replace(/[^\p{L}\p{N}]+/gu, " ")
    .trim()
    .replace(/\s+/g, " ");
}

const round = openRoundToken(body.roundToken);
const accepted = [round.answer, ...round.aliases].map(normalizeGuess);
const correct = accepted.includes(normalizeGuess(guess));
Code language: JavaScript (javascript)

This permits harmless differences in case, spacing, punctuation, and diacritics without weakening the token. The route returns the answer only after a correct guess or an explicit reveal.

Generation is only the beginning of the image lifecycle. Cloudinary transformations let the game create several views from one stored original. MysteryClueImage maps the current stage to CldImage transformation props:

if (stage === 0) {
  return <CldImage {...commonProps} blur="1600" crop="fill" gravity="auto" />;
}

if (stage === 1) {
  return <CldImage {...commonProps} pixelate="42" crop="fill" gravity="auto" />;
}

if (stage === 2) {
  return (
    <CldImage
      {...commonProps}
      crop={{ type: "thumb", gravity: "auto", zoom: "1.35" }}
    />
  );
}

return <CldImage {...commonProps} crop="fit" />;
Code language: JavaScript (javascript)

Deep blur makes the opening clue difficult. Pixelation exposes the broad palette and silhouette. The automatic-gravity thumbnail uses Cloudinary’s content-aware automatic gravity to create a close clue without requiring manually stored crop coordinates. The final stage fits the full image.

These are derived assets. Cloudinary preserves the original, creates a transformed version when its URL is first requested, and caches it for later delivery. You can therefore change the clue sequence without regenerating the AI image. Read the image transformations guide for more combinations.

The proof view adds f_auto and q_auto to a 960 by 960 automatic-gravity fill. The Next.js transformation guide explains how these delivery parameters select an efficient format and quality for the requesting context.

After solving a round, the player can describe a variation such as “turn this into a paper-cut poster at sunset.” Cloudinary’s image_to_image endpoint accepts managed assets by immutable asset ID. The remix route does not trust an arbitrary client asset ID. It first opens the solved round token, reads the source through the Admin API, and confirms that the returned public ID and configured generation folder match the token.

The generation helper then calls /image_to_image with a managed reference:

payload: {
  prompt: `Using [1] as the reference image, ${prompt}`,
  reference_images: [
    {
      asset_id: referenceAssetId,
      source_type: "managed_asset",
    },
  ],
}
Code language: JavaScript (javascript)

Flux, Recraft, GPT Image, and Nano Banana currently provide edit-capable models in the project. Recraft supports the standard edit tier only, and Ideogram is omitted because the current catalog does not list an edit model for it.

The remix is stored as another managed asset and automatically receives Cloudinary’s image-to-image generation tag and context. This turns a completed game round into a second creative workflow while retaining source-of-truth asset checks.

The /generations page is not a client-side history array. src/lib/cloudinary/generations.ts queries resources_by_tag for both text-to-image and image-to-image in parallel, combines and de-duplicates results by asset ID, checks the configured folder, and sorts them by creation time.

Requesting tags: true and context: true exposes the provenance Cloudinary stored with each asset:

  • Generation type
  • Model ID
  • Prompt
  • Seed when supported
  • Asset folder and public ID
  • Dimensions, format, bytes, and creation time

This makes the gallery durable across page refreshes and application restarts. It also illustrates when to use the Admin API: Use delivery URLs for pixels, but use authenticated server-side Admin API calls when your interface needs asset records and metadata. Never call the Admin API directly from the browser.

Once a player knows the answer, the proof route opens the solved token and performs two checks concurrently:

  1. It reads the immutable asset ID from the Cloudinary Admin API.
  2. It sends a HEAD request to the transformed delivery URL.

The panel reports stored generation context plus the delivery hostname, HTTP status, content type, transformation, response time, cache header, and observed multi-CDN edge headers. This is more useful than placing a decorative “multi-CDN powered” label on the screen because it connects the claim to the current asset and request.

Cloudinary’s Enterprise multi-CDN options include dynamic switching and smart selection while application URLs remain stable. Dynamic switching can route each request using current performance and availability data, while smart selection maps asset types or audiences to an appropriate network. The panel reports only the edge headers it observes and does not claim that Enterprise multi-CDN routing is active on every account. Review Cloudinary’s multi-CDN solutions for availability, routing behavior, and pricing implications.

Text-to-image rounds and image-to-image remixes consume add-on quota. Cloudinary documents the current credit model in the Image Generation plans and quota section. A public demo needs a visible, predictable boundary.

Pixel Pursuit permits two generation calls per browser. A server-created HttpOnly cookie stores the count and an HMAC-SHA256 signature. A visitor cannot raise or lower a valid count from client JavaScript, and both generation routes consume the same allowance. At zero remaining calls, the API returns HTTP 429 with the repository URL, and the interface explains how to clone and configure the project.

This guard is intentionally demo-grade. Cookies identify a browser, not a person, and can be cleared. If you need strict usage enforcement, add authentication and persist counters in a transactional database or shared rate limiter. Also combine application limits with Cloudinary account alerts and quota monitoring.

Start the development server:

npm run dev

Open http://localhost:3000 and test this sequence:

  1. Read and dismiss the first-visit game guide.
  2. Select a model family and tier, then generate a round.
  3. Submit incorrect guesses and confirm the clue becomes clearer.
  4. Solve or reveal the round and open the Cloudinary proof.
  5. Create a reference-image remix if one generation remains.
  6. Visit /generations and confirm both assets and their metadata appear.
  7. Try another generation and confirm the two-call gate offers clone instructions.

Before deployment, run:

npx tsc --noEmit
npm run lint
npm run build

Configure the same environment variables in your hosting platform. Keep the API secret and demo signing secret server-only. If your platform has route duration controls, retain enough time for the project’s 120-second generation request timeout.

Pixel Pursuit makes image generation observable from prompt to delivery. Cloudinary creates and stores the asset, Next.js protects the game state, URL transformations produce the clue sequence, the Admin API supplies durable provenance, and optional Enterprise multi-CDN delivery can complete the path to the player.

That combination is useful beyond games. The same pattern can support campaign review tools, creative variant explorers, product visualization workflows, or moderated content pipelines. Start with the repository, connect your own Cloudinary account, and replace the prompt catalog and clue sequence with the experience your users need.

Ready to level up your media workflow? Start using Cloudinary for free and build better visual experiences today.

Does Cloudinary store the AI-generated images?
Yes, when target_type is managed_asset, the generation result is stored in your Cloudinary product environment. The response includes an asset ID, public ID, and secure URL.
Why use both a public ID and an asset ID?
Use the public ID to build transformation and delivery URLs. Use the immutable asset ID to read and validate the exact resource through authenticated server-side operations.
Does every model support seeds?
No. The current app sends seeds for Flux, Nano Banana, and Ideogram. It omits the field for Recraft and GPT Image. Confirm the latest matrix in the Image Generation model documentation.
Does every text-to-image model support image editing?
No. The current edit catalog includes Flux, Recraft, GPT Image, and Nano Banana. Recraft currently has only a standard edit option, and Ideogram is not included.
Do Cloudinary transformations overwrite the generated image?
No. The managed original remains intact. Cloudinary creates derived versions for requested transformation URLs and caches them for subsequent delivery.
Is multi-CDN enabled automatically for every account?
No. Dynamic switching and smart selection within Cloudinary’s multi-CDN solutions are Enterprise capabilities. Check your account and the multi-CDN documentation before describing either capability as enabled.
Is the two-generation limit secure enough for production billing?
No. It is a signed per-browser demo guard. Production-grade per-user enforcement requires authentication and durable server-side usage storage.

Start Using Cloudinary

Sign up for our free plan and start creating stunning visual experiences in minutes.

Sign Up for Free