Livequest Studio helps newsrooms and creators cover live events with a fast editor, global analytics, polished embeds, and sponsorship tooling. It is built with Next.js App Router, Supabase, and Tailwind CSS.
- Studio workspace: Coverage, Planner, Analytics, and Sponsors tabs keep every liveblog in one place with autosave, scheduling, templating, and row-level security.
- Account intelligence:
/accountsurfaces active liveblogs, audience reach, sponsor performance, and referrers so you can spot trends without exporting data. - Sponsorships & monetisation: Manage reusable sponsor slots, flight windows, and real-time CTR tracking for every placement across embeds.
- Realtime storytelling: Keyboard-first composer, pinning, instant media uploads, Server Actions, SSE embed feeds, and optional push notifications for subscribers.
- Team-ready controls: Supabase auth, privacy modes, folder organisation, concurrency helpers, Discord broadcast webhooks, and scheduled publishing.
- Sports integrations: API-Football sync jobs, match centre templates, and fixtures API endpoints to power scoreboards and pre-built match commentary.
- Operational tooling: Supabase migrations, cron utilities, Sentry instrumentation, and TypeScript components for confident iteration.
- Next.js 15 App Router with React 19, Server Actions, and incremental revalidation (
src/app). - Supabase for authentication, Postgres storage, row-level security policies, storage buckets, and pg_cron scheduling (
supabase/migrations). - Tailwind CSS v4/PostCSS pipeline with custom shadcn-inspired UI components (
src/components). - Sentry for observability (
sentry.client.config.ts,sentry.server.config.ts,src/instrumentation.ts). - Streaming updates delivered via Supabase Realtime + Server-Sent Events (
src/app/api/embed/[id]/sse).
src/app: App Router routes for marketing pages, auth, dashboard, embed, API handlers, and Livequest management UI.src/components: Shared UI elements, including layout chrome and Livequest-specific widgets.src/lib: Data-access utilities (Supabase clients, football integrations, Discord helpers, shared utils).supabase/migrations: Database schema (Livequests, updates, analytics, matches, cron helpers) applied through Supabase CLI.public: Static assets bundled with Next.js build.Dockerfile&.dockerignore: Multi-stage image used by the Cloudflare container runtime.cf-container-worker/: Worker project that builds, deploys, and manages the container + Durable Object integration.
- Visit live documentation (or
/docslocally) for embeds, push notifications, and the operations runbook.
- Node.js 20.x (the project is built and tested against the current LTS release).
- npm 10.x (bundled with Node 20) or another package manager (
pnpm,yarn, orbun). - Supabase project access. Install the Supabase CLI if you plan to run migrations locally.
Create web/.env.local (not committed) and add the following environment variables. Replace placeholder values with your own project secrets—do not paste production credentials into documentation or source control.
| Variable | Required | Purpose |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
✅ | Supabase project URL used by browser and server clients. |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
✅ | Supabase anon key for public client-side operations. |
SUPABASE_SERVICE_ROLE_KEY |
✅ (server only) | Service role key used by server actions that need elevated privileges (e.g. imports, scheduled publishing). |
NEXT_PUBLIC_BASE_URL |
➖ | Base URL of the deployed app (used to generate embed snippets). Set to http://localhost:3000 for local dev. |
NEXT_PUBLIC_SITE_URL |
➖ | Canonical marketing URL used in outbound links, scheduled publish notifications, and push payloads. |
APIFOOTBALL_KEY |
➖ | API-Football key for live match ingestion endpoints. Required to run /api/matches/sync. |
FOOTBALL_DATA_KEY |
➖ | Optional Football-Data.org API key for alternative fixture sourcing. |
CRON_SECRET |
➖ | Shared secret protecting scheduled sync endpoints (/api/matches/sync, /api/matches/complete, scheduled publish). |
SENTRY_DSN |
➖ | Sentry project DSN to enable error and performance monitoring. |
NEXT_PUBLIC_VAPID_PUBLIC_KEY |
➖ | Public VAPID key that enables browser push notifications on embeds. Required if push is enabled. |
VAPID_PRIVATE_KEY |
➖ | Private VAPID key paired with the public key for sending pushes. |
VAPID_SUBJECT |
➖ | Contact string (usually mailto:) attached to push notifications. |
NEXT_PUBLIC_INTERCOM_APP_ID |
➖ | Intercom Messenger app ID (bbrbwgix for the shared workspace). |
Voice dictation has been removed; no OpenAI configuration is required.
You may also want to configure Supabase storage bucket media (public) for image uploads.
Link or start a Supabase project before running the app.
- Authenticate the CLI and link a project:
supabase login supabase link --project-ref YOUR_PROJECT_REF
- Apply the database schema:
supabase db push # or run migrations individually supabase migration up - Ensure the
mediastorage bucket exists and allows public reads (used for uploaded assets).
cd web
npm install
npm run devVisit http://localhost:3000 to access the marketing page. Sign up or sign in to reach the dashboard (/dashboard) and start creating Livequests. Other useful commands:
npm run build– production build (uses Next.js Turbopack).npm run start– run the compiled production server.npm run lint– lint the codebase with ESLint.
/dashboardlists every liveblog with folder filters, privacy states, and status chips so you can archive, complete, or delete coverage quickly.- Create new liveblogs with templates, default sponsors, and folder assignment directly from the
CreateLiveblogDialog. - Each Livequest ships with four workspaces (
ManageTabs):- Coverage: Keyboard-first composer with autosave, pinning, sponsor assignment, and media uploads.
- Planner: Draft, schedule, or queue updates. Publishing fires Discord broadcasts and optional push notifications.
- Analytics: Real-time uniques, starts, session concurrency, and 24h trends from
viewer_pingsandanalytics_events. - Sponsors: Manage reusable sponsor slots, flight windows, creative assets, and live CTR metrics.
- Privacy, ordering, templates, and embed defaults live on each Livequest (
settingscolumn) and can be adjusted through the settings dialog.
/accountcentralises profile preferences plus a global snapshot of your coverage footprint.- Account analytics aggregate across every liveblog: active vs archived counts, audience reach (7/30 day), session heartbeats, and total updates.
- Drill into top liveblogs, referrer domains, and sponsor performance with quick links to the dedicated analytics workspace.
- Sponsor insights roll up impressions, clicks, and CTR so you can compare partners at a glance.
- The dashboard provides embed snippets via
EmbedButton. You can choose between iframe and inline script. - Inline script usage:
<div data-Livequest-id="Livequest_ID" data-mode="native" data-order="newest"></div> <script src="https://your-domain/embed.js" async></script>
data-modesupportsiframe(default) ornativerendering.data-ordercan benewestoroldest.
- Embeds fetch
/api/embed/:id/feed, subscribe to/api/embed/:id/sse, and fall back to polling withdata-mode="native"handled by a Shadow DOM renderer. - Analytics pings occur automatically via
/api/embed/:id/trackand feed into Supabase tables.- Feed supports ETag-based conditional GET and cache hints (
stale-while-revalidate). - CORS can be restricted by setting
EMBED_ALLOW_ORIGINS(comma-separated origins). Responses setVary: Origin. - A local demo page is available at
/embed-demo.htmlto test iframe/native.
- Feed supports ETag-based conditional GET and cache hints (
- Sponsor slots live on each liveblog and can be reused across coverage to keep brand assets consistent.
- Slots support status windows (
scheduled,active,completed), optional logos, CTA URLs, affiliate codes, and layout presets. - The embed tracks impressions and clicks automatically, syncing data to
sponsor_impressionsandsponsor_clicksfor 30-day reporting. - Account and liveblog analytics expose CTR, impressions, and click totals so partners see performance in real time.
- Embed readers can opt into browser push notifications (service worker served from
/push-sw.js). - Configure
NEXT_PUBLIC_VAPID_PUBLIC_KEY,VAPID_PRIVATE_KEY, andVAPID_SUBJECTfor web-push support. - Publishing an update triggers push payloads automatically; manual broadcasts are available via
POST /api/liveblogs/{id}/broadcast/notify.
- The edge runtime can lazy-load a pre-bundled copy of
@supabase/ssrwhenSUPABASE_SSR_MODULE_URLis set. Bundle the module withesbuild(seenpx esbuild node_modules/@supabase/ssr/dist/module/index.js --bundle --format=esm --platform=browser --target=es2022 --minify --outfile=supabase-ssr.bundle.mjs), upload it to public storage (e.g. Cloudflare R2), and point the env var at the resulting URL. - If the variable is not provided, the app falls back to the npm package, which increases the edge worker size.
- Add a Discord webhook URL in the Livequest settings to mirror updates to a channel (
discord_webhook_urlinsidesettings). - The planner triggers
/api/Livequests/:id/broadcast/discordwhenever you publish from the UI. - Messages are formatted in
src/lib/integrations/discord.tswith optional image embedding for Supabase-hosted media.
/api/matches/syncingests fixture data from API-Football for the lastdayswindow (defaults to 7). Protect it withCRON_SECRET./api/matches/completemarks matches as finished;/api/matcheslists fixtures with filtering by country, league, status, and date range.- To run a manual sync:
curl -X POST "https://your-domain/api/matches/sync" \ -H "Content-Type: application/json" \ -H "x-cron-secret: $CRON_SECRET" \ -d '{"days":7}'
- The Supabase migrations include
app_configkey-value storage plus secure helpers (call_matches_sync,call_matches_complete) so you can schedule jobs withpg_cron.insert into app_config(key, value) values ('sync_url', 'https://your-domain/api/matches/sync'), ('complete_url', 'https://your-domain/api/matches/complete'), ('cron_secret', 'YOUR_CRON_SECRET') on conflict (key) do update set value = excluded.value;
The production site runs inside a Cloudflare container that is fronted by a Worker + Durable Object. The worker project lives in cf-container-worker/ and uses the root Dockerfile for builds.
cd /Users/<you>/Liveblogo
docker build -t livequest-app .
docker run --rm -p 3100:3000 \
--env-file web/.env.local \
livequest-app
# visit http://localhost:3100cd cf-container-worker
npm install # first run only
npx wrangler deployWrangler builds a new image, pushes it to Cloudflare’s registry, and publishes the Worker.
npx wrangler tail livequest-container-worker --format=prettyTo scale CPU/RAM, change the container instance_type in cf-container-worker/wrangler.toml (for example standard-2) and redeploy. Pricing is usage-based, so you only pay for what actually runs.
If you ever need to trim the Worker bundle, host a bundled copy of @supabase/ssr (for example in R2) and set SUPABASE_SSR_MODULE_URL to that URL. When unset, the container loads the library from node_modules.
- Viewer heartbeats land in
viewer_pingsand session events inanalytics_events(supabase/migrations/0002_analytics.sql). - Use
public.count_concurrent_viewers(liveblog_id)(0003_concurrent_viewers.sql) to calculate active sessions in the last 30 seconds. - Account-wide helpers (
account_analytics_summary,account_top_liveblogs,account_top_sponsors,account_top_referrers) live in0012_account_analytics.sqlfor reporting rollups. - Analytics are surfaced across the manage page, account workspace, and can be extended for bespoke dashboards.
- Sentry is optional but recommended. Supply a
SENTRY_DSNto enable automatic tracing wrapped around database calls (src/app/api/embed/[id]/feed/route.tsandsrc/instrumentation.ts).
- Production runs on Cloudflare Containers via
cf-container-worker/. - Ensure required secrets are stored with
npx wrangler secret put …(service role, Stripe, etc.). - To deploy:
cd cf-container-worker && npx wrangler deploy. - Container size can be tuned via
instance_type(lite,standard-1,standard-2, …). Larger classes improve SSR performance but cost more while running. - Supabase migrations must be applied to production before deploying new schema-dependent changes.
npm run dev– Turbopack dev server with hot reload.npm run build– Production bundle.npm run start– Serve production bundle locally.npm run lint– ESLint (runs againstsrc).npx wrangler deploy(fromcf-container-worker/) – Build and publish the container/Worker.
- Keep
.env.localout of version control; use.env.examplewith placeholders if you need to share configuration with collaborators. - If embeds do not update in real time, ensure Supabase Realtime is enabled on the
updatestable and that the browser can reach/api/embed/:id/sse. - Cron jobs require the
pg_cronandpg_netextensions (enabled in migrations0005and0006). Check the Supabase dashboard if schedules are not firing. - When working locally against a remote Supabase project, use
NEXT_PUBLIC_BASE_URL=http://localhost:3000so generated embed snippets point to your dev server.