Intermediate30 minModule 1 of 4

Design the App

Architecture decisions, data models, and user experience flow — the decisions that matter before writing code.

Start with the user, not the API

Before choosing models or writing endpoints, understand what the user actually does in your app.

The core user journey for an image studio:

Yes No Open app Type prompt Select model optional Click Generate See loading state Image appears Happy? Download or share View in gallery

Every technical decision should serve this journey. Keep it out of the user's way.

Architecture overview

We'll build on the Next.js App Router stack:

app/
├── page.tsx                  ← Studio (prompt + result)
├── gallery/page.tsx          ← Generation history
├── api/
│   ├── generate/route.ts     ← Create prediction + store job
│   ├── predictions/[id]/     ← Poll status
│   └── webhooks/route.ts     ← Skytells callback
└── components/
    ├── PromptInput.tsx
    ├── GenerationResult.tsx
    └── GalleryGrid.tsx

Data model

Three tables cover everything we need:

-- generations: tracks each prediction
CREATE TABLE generations (
  id          TEXT PRIMARY KEY,       -- Skytells prediction ID
  user_id     TEXT NOT NULL,
  prompt      TEXT NOT NULL,
  model       TEXT NOT NULL DEFAULT 'truefusion-pro',
  status      TEXT NOT NULL DEFAULT 'queued', -- queued|processing|succeeded|failed
  output_url  TEXT,                   -- our storage URL (not CDN, which expires)
  error       TEXT,
  created_at  TIMESTAMPTZ DEFAULT now(),
  completed_at TIMESTAMPTZ
);

-- users: simple auth (use Clerk, Auth.js, or similar)
CREATE TABLE users (
  id         TEXT PRIMARY KEY,
  email      TEXT UNIQUE NOT NULL,
  plan       TEXT DEFAULT 'free',
  created_at TIMESTAMPTZ DEFAULT now()
);

We deliberately skip a prompts table — prompts live on generations. Keep it simple.

UX decisions

Make these calls early — they affect your architecture:

1. Optimistic UI or wait?

Recommendation: Optimistic with polling

Show the prompt in the gallery immediately with a shimmer loading state. Poll /api/predictions/:id every 2 seconds until complete. This feels instant to the user.

Avoid holding the browser open for the full generation — use webhooks for the backend and polling on the frontend.

2. How many concurrent generations?

Recommendation: 1 per user on Free, 3 on Pro

Implement this in your backend, not the frontend. The frontend can disable the "Generate" button, but enforce limits server-side.

3. What happens to the output URL?

Critical: Skytells CDN URLs expire after 24 hours. Download and re-upload to your own storage (Vercel Blob, Supabase Storage, S3) immediately when the webhook fires.

4. Which models to expose?

Start with 3 choices to avoid choice paralysis:

LabelModel IDDescription
Standardtruefusion-proDefault — best balance
Fast previewtruefusion-edgeQuick draft (512×512)
High qualitytruefusion-2.0Slower, maximum detail

Tech stack choices

ConcernChoiceWhy
FrameworkNext.js 14+ App RouterWebhooks + SSR + API in one project
DatabasePostgres via PrismaSimple schema, great DX
StorageVercel Blob or SupabaseZero-config, generous free tier
AuthClerk or Auth.jsPlug-and-play, handles sessions
StylingTailwind CSSFast prototyping, consistent
DeploymentVercelZero-config Next.js deploy

Summary

Before writing code:

  • Map the user journey — generation should feel instant via optimistic UI
  • Use a simple 2-table schema: users + generations
  • Always persist outputs to your own storage — CDN URLs expire
  • Expose 3 models maximum to start — you can always add more
  • Enforce usage limits server-side, not just in the UI

In the next module, you'll build the backend API.

On this page