← projects

Nani

Nani app overview: library grid, prompt editor, and generations in progress
← projects

The frontier model lives in a chat window.

chatgpt.com image generation interface showing a single image
← projects Annotated chatgpt.com screenshot calling out the missing library, single-image generation, unsaved prompts, and re-uploaded references
← projects
The Market

Dedicated image tools earn revenue.

Ideogram and Midjourney show that users pay for image tools. Midjourney reached 20 million users without VC funding or paid marketing.

Midjourney registered users, via Discord
year end · millions

demandsage.com · jan 2026

← projects
Goals & Team

Three goals, a team of two.

the goals
01

ship the mvp fast

Ship a working product to users in about two weeks.

02

prove the product

Get real sales from new users within weeks of launch.

03

read the growth

After a few weeks, use the results to decide whether to keep investing.

the team

Me

design · front end · back end · marketing

GPT-5.2

pair-programmer · the latest gpt model at kickoff

← projects
The Product

Nani puts a workflow around GPT Image 2.

Nani home: prompt composer with set chips up top, generation settings beside it, and the image library filling the grid below
← projects
The Architecture

I built Nani with six pieces.

https · json · sync generate · async sql · storage · sync webhooks · async store images · cdn client React SPA react 19 · typescript vite · supabase-js api · serverless Netlify Functions generate · credits · shares 10s cap per invocation ? generation GPT Image 2 via fal.ai · 1–2 credits / image platform Supabase auth · postgres · storage ? payments Stripe credit checkout ? delivery Cloudflare r2 · images cdn why fal.ai? One adapter file wraps fal.ai. Swapping GPT Image 2 for the next model changes one file. why credits? Users buy packs and spend one or two credits per image. No subscription or free tier. why cloudflare? I store one original in R2. Cloudflare resizes and converts formats at the edge per request.
← projects
The Billing

Credits connect purchases to generations.

purchase credits granted 1 credit reserved payments Stripe Checkout bundles · $5 to $100 netlify function Webhook verified · idempotent supabase Credit Ledger balance + transactions the product Generation 1 credit per image
~$0.30 one credit bundles from $5 to $100
$0.02–0.22 one generation price varies by quality
every image sold above cost 4k costs 2 credits

I skipped monthly plans and pricing tiers to keep billing simple.

← projects
Key Decision · The Pipeline

From prompt to saved image.

React SPA Netlify Functions GPT Image 2 Supabase R2 + CDN 01 generate · prompt + refs 02 reserve credits · atomic ? 03 queue job · async · requestId loop · per job · concurrent ? 04 poll status · queue position 05 store · image to r2 ? 06 settle · metadata + credits ? 07 deliver · images via cdn credits: reserve first Functions reserve credits before generation. On failure, they release the hold without a charge. why polling? Netlify Functions cap at ten seconds, so each job polls on its own while other jobs run. idempotent completion Concurrent polls race to store results. A unique requestId prevents duplicate completion. settle last Functions deduct credits after the image reaches R2 and Postgres. Failed runs release the hold.
← projects
The Decisions · Tradeoffs

Three decisions, and the alternatives.

01

the model provider

ruled out

Calling the OpenAI Images API directly. One vendor's contract in my code, and no queue in front of a slow model.

chose

GPT Image 2 via FAL, behind a one-file adapter. FAL queues and retries the jobs, and the next model swap touches one file.

02

a slow job, a short function

ruled out

Holding the request open. FAL's direct endpoint blocks until the image lands, well past Netlify's ten-second cap. Webhooks would work, but overkill here.

chose

FAL's queue. Submit hands back a job id right away, then the browser polls that id. Every call is short, and jobs in flight poll side by side.

03

pricing

ruled out

Subscriptions and tiers. Trial users already pay OpenAI monthly, and each tier is a plan to build and maintain.

chose

One-off credit packs. One or two credits per image, sold above cost.

← projects
UX Details

Sets work as mentions.

app.getnani.com Nani prompt composer with an @StormDragon image-set chip showing a three-image thumbnail popover, plus #FantasyEpic and #GoldenHour prompt-set chips
fig. i · @stormdragon carries three images; #fantasyepic and #goldenhour carry text

what the model receives

@StormDragon soaring over mountains #FantasyEpic #GoldenHour

  1. 01

    # prompt sets become text

    …mountains, epic fantasy style, golden hour light

  2. 02

    @ image sets become images

    image_urls = [ storm-1, storm-2, storm-3 ]

  3. 03

    the server labels each image batch

    [Reference: images 1-3 are @StormDragon]

    + prompt  →  GPT Image 2

← projects
UX Details

Organize it, then share it.

/organize Nani library with three images selected, a stacked-thumbnail drag ghost under the cursor, and folders in the sidebar lighting up as drop targets
fig. ii · three selected; one drag carries all of them
  1. 01

    native html5 drag and drop

    into folders · into the prompt · from your OS

  2. 02

    replace the default drag preview

    stacked thumbnails follow the cursor

← projects
The Impact

How it went.

the goals
01

ship the mvp fast

2 weeks

I shipped the live product in two weeks, as planned.

02

prove the product

40–50 paying users

New users bought credit bundles within weeks of launch.

03

read the growth

300–400 sign-ups

Within two weeks, 300 to 400 users signed up for five trial credits.

extra wins

~1 in 8 converted to paid

free to paying, within two weeks

Paid generations cleared their cost

margin from the first sale

← projects
Learnings

Building was the easier part.

  1. In a crowded market, SEO and marketing mattered as much as the product.
  2. Trial users told me they already paid for OpenAI and did not want a second image subscription.
  3. What I'd invest more in: user research. I talked to users, but screen recordings and paid interviews would have narrowed the scope sooner.
  4. I should have built the community earlier. Prompt sharing, guides, and booklets could have given users a reason to stay.