How the Next.js dashboard keeps the media library and result preview in sync without a full page refresh.
- User submits the generation form → server action creates
generated_mediarow (pending→processing). - Pixio completes →
POST /api/webhooks/pixiosetsstatus: completedandmedia_url. - UI updates via Supabase Realtime, custom DOM events, and polling fallbacks.
Apply on your remote project (supabase db push from apps/web, or run SQL manually). Migration files live in apps/web/supabase/migrations/ in this monorepo.
| Migration | Purpose |
|---|---|
20260619120000_enable_generated_media_realtime.sql |
Adds generated_media to supabase_realtime publication |
20260619130000_create_generated_media_storage_bucket.sql |
Public generated-media bucket + storage RLS |
Without Realtime, the library and preview still update via polling (every 3s while in-flight).
| File | Role |
|---|---|
apps/web/src/components/dashboard/media-generation-form.tsx |
Form + right-side result preview |
apps/web/src/components/dashboard/media-library.tsx |
Library grid + Realtime + polling |
apps/web/src/lib/constants/media-events.ts |
pixio:generation-started, pixio:media-record-updated |
packages/supabase/src/lib/client.ts |
Singleton browser Supabase client (stable Realtime) |
apps/web/src/app/api/webhooks/pixio/route.ts |
Webhook → storage upload → completed row |
The preview panel uses one slot (not layered overlays):
- Loading — spinner while the active generation has no
media_urlyet. - Result — image/video replaces the spinner in the same area when
status === completedandmedia_urlis non-empty. - Empty — placeholder when there is no prior result.
- Failed — error state when generation fails.
Loading stays visible until resultMediaId === currentMediaId and a trimmed non-empty media_url exists. Empty-string URLs from the initial insert do not count as a result.
The form polls the active row every 3s, subscribes to Realtime for that id, and listens for pixio:media-record-updated events dispatched from the library.
- On
pixio:generation-started, fetches the new row immediately. - Realtime channel per user on
generated_mediaINSERT/UPDATE/DELETE. - Polls every 3s while any row is
pendingorprocessing. syncRecentMediaupserts rows (does not only append).
Pixio must reach your webhook URL. For local dev:
- Run ngrok (or similar) to expose port 3000.
- Set
NEXT_PUBLIC_SITE_URLto the tunnel URL. - Configure Pixio deployment webhooks to
{SITE_URL}/api/webhooks/pixio.
| Symptom | Likely cause |
|---|---|
| Library updates only after refresh | Realtime migration not applied; check browser console for CHANNEL_ERROR |
| Preview stuck on loading forever | Webhook not reaching app; row never gets media_url |
| Preview empty after loading stops | Row completed but media_url still blank — check webhook logs |
| Bucket not found on upload | Storage migration not applied |