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:
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.tsxData 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:
| Label | Model ID | Description |
|---|---|---|
| Standard | truefusion-pro | Default — best balance |
| Fast preview | truefusion-edge | Quick draft (512×512) |
| High quality | truefusion-2.0 | Slower, maximum detail |
Tech stack choices
| Concern | Choice | Why |
|---|---|---|
| Framework | Next.js 14+ App Router | Webhooks + SSR + API in one project |
| Database | Postgres via Prisma | Simple schema, great DX |
| Storage | Vercel Blob or Supabase | Zero-config, generous free tier |
| Auth | Clerk or Auth.js | Plug-and-play, handles sessions |
| Styling | Tailwind CSS | Fast prototyping, consistent |
| Deployment | Vercel | Zero-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.