cd /news/ai-products/how-i-built-an-edge-first-ai-cartoon… Β· home β€Ί topics β€Ί ai-products β€Ί article
[ARTICLE Β· art-153543] src=dev.to β†— pub= topic=ai-products verified=true sentiment=↑ positive

How I Built an Edge-First AI Cartoon SaaS with Next.js, Cloudflare Workers & D1

An independent developer built AI Cartoon Generator, a SaaS creative studio for cartoon, anime, and comic creators, running entirely on Cloudflare Workers with Next.js App Router via OpenNext, Cloudflare D1 for serverless SQL, and R2 for image storage and caching. The platform centers on a locked "Character Asset" reference anchor that preserves character identity across pose, expression, and background variations, feeding storybook and animation pipelines where an LLM plans narrative beats before image generation. The developer reports that splitting SSR, thumbnail jobs, LLM planning, and webhooks across isolated Workers and background queues was necessary to stay within Worker limits.

by read5 min views5 publishedOct 20, 2026

As an indie maker, launching an AI-powered SaaS often presents a brutal dilemma:

I chose the second path.

Over the past few months, I built and launched AI Cartoon Generator β€” a specialized creative studio designed for cartoon, anime, and comic creators. Instead of relying on a monolithic server, the entire platform runs on Cloudflare Workers using Next.js App Router (via OpenNext), Cloudflare D1, and R2.

In this article, I want to show what we built, lay bare the architectural blueprint, and share the hardest battle-tested lessons learned running Next.js at the edge.

Most generic AI image generators produce stunning one-off pictures, but they fail creators who want to tell sequential stories or build consistent IP:

To solve this, we focused on three core pillars at AI Cartoon Generator:

Character Asset. Using an active reference anchor, users can generate pose, facial expression, and background variations without losing identity. A typical result β€” an ordinary photo restyled as a hand-painted anime scene:

Input: the user's original photo

Output: Ghibli-style transformation, generated on the edge

And here's the character continuity system in action. This is one locked Character Asset (Mina the Pocket Illustrator) fed through four independent variation requests β€” same face, same outfit, different pose, expression, and world:

The storybook pipeline then turns a character like this into a multi-scene illustrated book, with an LLM planning the narrative beats before any image is generated:

A generated storybook cover (character: Iris Vale, a mountain map archivist)

While the animation pipeline produces multi-shot storyboard sequences from the same identity anchor:

Four-scene animation storyboard generated from one character reference

Running a dynamic Next.js App Router site with heavy background AI orchestration on Cloudflare Workers requires strict isolation. A single Worker trying to handle SSR, image resizing, LLM planning, and webhooks will quickly crash into worker limits.

Here is our production topology:

                                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                  β”‚      Cloudflare Edge        β”‚
                                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                                 β”‚
                                                 β–Ό
                                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                    β”‚   Main Site Worker      β”‚
                                    β”‚   (Next.js / OpenNext)  β”‚
                                    β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜
                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              └───────────────────┐
                     β–Ό                                                      β–Ό
           β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”‚ Cloudflare D1     β”‚                                  β”‚ Cloudflare R2     β”‚
           β”‚ (Serverless SQL)  β”‚                                  β”‚ (Images & Cache)  β”‚
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β–²                                                      β–²
                     β”‚                                                      β”‚
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Background Queues & Workers                                           β”‚                β”‚
    β”‚                                                                       β”‚                β”‚
    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚                β”‚
    β”‚  β”‚ media-thumbnail-jobs  │──▢│ Main Worker Thumbnail Handler  │──────── (WebP 512px)   β”‚
    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚                β”‚
    β”‚                                                                       β”‚                β”‚
    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚                β”‚
    β”‚  β”‚ storybook-planning    │──▢│ Standalone Planning Worker     │──────── (LLM planning) β”‚
    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚                β”‚
    β”‚                                                                       β”‚                β”‚
    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚                β”‚
    β”‚  β”‚ animation-planning    │──▢│ Standalone Planning Worker     β”‚       β”‚                β”‚
    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚                β”‚
    β”‚                                                                       β”‚                β”‚
    β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚                β”‚
    β”‚  β”‚ storybook-jobs        │──▢│ Video Container Worker (Docker)β”‚β”€β”€β”€β”€β”€β”€β”€β”˜ (MP4 renders)  β”‚
    β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                        β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

ai-cartoon-generator) batch_size = 1 and isolated memory to plan scripts and scene breakdowns.linux/amd64). Deploying Next.js to Cloudflare Workers via OpenNext isn't just "click deploy and relax." Here are the real challenges we hit in production and how we resolved them:

In our early iterations, the main Next.js worker did everything: served HTML, queried D1, streamed LLM responses for story generation, and processed webhooks.

During traffic spikes or heavy narrative planning tasks, the V8 isolate hit Cloudflare's memory ceiling, resulting in fatal Error 1102: Worker exceeded memory limits.

The Solution:

Treat the main worker purely as an interactive UI and API layer. We offloaded LLM-heavy streaming pipelines into dedicated planning workers (aicartoon-storybook-planning and aicartoon-animation-planning) managed by Cloudflare Queues with dead-letter queue (DLQ) fallbacks. If an LLM response runs wild, it never degrades website responsiveness.

populate-cache Timeout Nightmare When using opennextjs-cloudflare deploy, the built-in deploy step tries to push pre-rendered HTML/RSC cache entries to R2 using an HTTP proxy. As our localized landing pages and style showcase grew to hundreds of static routes, network throttling caused continuous deploy timeouts.

The Solution:

We decoupled cache population from deployment:

pnpm opennextjs-cloudflare build. upload-cache.mjs) that maps .open-next/cache entries to OpenNext cache keys (next-cache/<buildId>/<sha256(path)>). wrangler r2 object put calls with SHA-256 integrity verification.OPEN_NEXT_DEPLOY=true wrangler deploy. Deployments went from 10-minute nail-biters to a reliable 45-second pipeline.

If you're coming from Postgres or traditional SQLite, you might be accustomed to:

await db.transaction(async (tx) => {
  await tx.update(...);
  await tx.insert(...);
});

On Cloudflare D1, interactive transactions are not supported. If a step fails halfway, you risk partial state corruption (e.g., deducting user credits without logging the task).

The Solution:

We rely on D1's native batch() API combined with deterministic idempotency keys:

// Atomic batching ensures all-or-nothing execution across statement batches
const statements = [
  db.prepare(`UPDATE user_credits SET balance = balance - ? WHERE user_id = ? AND balance >= ?`)
    .bind(cost, userId, cost),
  db.prepare(`INSERT INTO ai_tasks (id, user_id, status, idempotency_key) VALUES (?, ?, ?, ?)`)
    .bind(taskId, userId, 'PENDING', idempotencyKey)
];

const results = await d1Binding.batch(statements);

With Incremental Static Regeneration (ISR) on the edge (revalidate = 3600), the edge cache serves fresh pages immediately after deployment.

However, if your background revalidation handler or memory queue has a subtle bug (e.g., missing worker self-reference bindings), you won't notice it immediately! An hour later, when pages turn stale and trigger background re-renders, visitors will suddenly encounter 500 Internal Server Errors.

The Solution:

We enforced a two-tier verification workflow:

pnpm cf:check) Moving to an Edge-First serverless architecture paid off tremendously:

Building AI Cartoon Generator taught me that the Edge is no longer just for static blogs or simple redirect workers. With OpenNext, D1, R2, and Cloudflare Queues, you can build full-fledged, multi-tenant AI SaaS platforms on edge primitives.

If you are an artist or creator looking for consistent anime and cartoon styles, feel free to give AI Cartoon Generator a spin!

One of the newer style presets: Tim Burton-inspired gothic toons

To my fellow engineers:

I'd love to hear your experiences, war stories, and questions in the comments below! πŸ‘‡

── more in #ai-products 4 stories Β· sorted by recency
── more on @cloudflare 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/how-i-built-an-edge-…] indexed:0 read:5min 2026-10-20 Β· β€”