Skip to content

Repository files navigation

ThumbAI

Generate YouTube thumbnails and blog cover images from a prompt, an image you already have, or a blog URL.

Workflows

Route What it does
/studio/text-to-image Describe the thumbnail; the app writes a design prompt and generates it.
/studio/image-to-image Upload photos (a face, a product) and composite them into a thumbnail.
/studio/blog-cover Paste a blog URL. The page is fetched and read, and a cover image is designed from its actual content.

Every workflow outputs YouTube (16:9, 1280×720) and Shorts (9:16, 720×1280).

Stack

  • Next.js 15 (App Router) and React 19
  • Clerk for authentication
  • Prisma and PostgreSQL
  • Fal AI for image generation, OpenAI for prompt authoring
  • Razorpay for payments
  • Tailwind CSS v4 with shadcn/ui

Image models

Defined in one place, src/config/models.ts. Swapping a model is a change there rather than in the route handlers.

Tier Endpoint Cost Credits
Draft openai/gpt-image-2 (medium) ~$0.040 / image 1
Quality fal-ai/nano-banana-pro (2K) ~$0.150 / image 3
Edit fal-ai/nano-banana-2/edit ~$0.080 / image 2

Prompt authoring runs on PROMPT_MODEL in the same file, overridable with OPENAI_PROMPT_MODEL.

Getting started

git clone <repo> && cd thumbai
npm install                 # also runs prisma generate
cp .env.example .env        # then fill it in, see below
npm run db:up               # local Postgres via docker compose
npm run db:deploy           # apply migrations to it
npm run dev

Open http://localhost:3000.

Local and production databases are separate

Production runs on Neon. Local runs on a Postgres container, so next dev cannot reach production data.

File Holds Loaded
.env local DATABASE_URL plus all other keys automatically by Next
.env.prod only the production DATABASE_URL never automatically

.env.prod is not a Next.js convention, which is the point: nothing picks it up by accident. Production commands opt in explicitly and print the target host before acting.

Command Target
npm run db:up / db:down local container
npm run db:migrate local — create and apply a new migration
npm run db:deploy local — apply existing migrations
npm run db:studio local — browse data
npm run db:status:prod production — show migration state
npm run db:deploy:prod production — apply migrations
npm run db:backup:prod production — pg_dump into backups/

npm run build runs prisma migrate deploy first, so a deploy cannot ship code ahead of the schema it needs. That ordering has bitten twice: once when a String[] to enum change went out before the code that understood it, and once when a new column was added in code but never migrated. The build step is the only pre-deploy hook Vercel offers, so it is where this belongs.

Back up before migrating production: npm run db:backup:prod. Dumps land in backups/, which is gitignored because they contain real user data.

Admin tooling

node --env-file=.env tools/grant-admin.mjs                     # list users (read-only)
node --env-file=.env tools/grant-admin.mjs you@example.com 100 # promote + set credits

Email is not unique in the schema, so the script refuses to act when more than one row matches and prints the ids instead.

Environment

Copy .env.example and fill in every value — the app validates them at startup via src/config/env.ts and will refuse to boot if any are missing.

There is deliberately no NEXT_PUBLIC_FAL_KEY. Anything prefixed NEXT_PUBLIC_ is inlined into the browser bundle, so a Fal credential there is readable by any visitor and spendable against your account. Uploads go through /api/upload, which holds the key server side.

Webhooks

Two endpoints need to be reachable from the internet, so use a tunnel (ngrok http 3000) in development:

Provider Endpoint Purpose
Clerk /api/webhook/register Creates the local User row on sign-up. Without it, sign-in succeeds but every API call returns 401.
Razorpay /api/webhook/payment Grants credits after a verified payment.
Fal /api/fal/webhook Receives finished generations. Set NEXT_PUBLIC_FAL_WEBHOOK_URL to the tunnel origin.

Fal deliveries are verified against Fal's ED25519 JWKS, so they must arrive with their original signature headers intact.

Scripts

Command Purpose
npm run dev Development server
npm run build Production build
npm run start Serve the production build
npm run typecheck tsc --noEmit
npm run format Prettier

Architecture notes

Credits are server-authoritative. They are deducted inside the generation routes with a conditional update guarded on credits >= cost, so concurrent requests cannot overdraw, and refunded if the provider call fails. Nothing on the client can move a balance.

Generation is queued, not blocking. Both /api/generate and /api/edit submit to Fal's queue and return a request_id. The browser subscribes to /api/result-stream (SSE, owner-only, capped at 5 minutes) while Fal calls /api/fal/webhook with the result.

Plan pricing lives in src/config/plans.ts. Checkout sends only a plan id; the price charged and the credits granted are both resolved server side.

License

MIT

About

AI Youtube Thumbnail Generator - Nano Banana Model

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages