Circle AV Feed is a Next.js digital signage feed powered by the Circle Admin API (Circle community platform). It renders a full-screen “live feed” display for events: posts (text, image, video) in the main area with a rotating comments ticker.
- Main area: rotates through posts (image, video, or text-only).
- Bottom bar: shows comments for the currently displayed post (or a CTA when a promo slide is showing).
- Config page (
/config): per-device settings stored in localStorage (space selection + fetch limits + theming + assets + import/export).
Run the dev server:
npm run dev- Signage screen:
http://localhost:3000 - Config screen:
http://localhost:3000/config
npm run build
npm run start- The client polls
GET /api/circle/postsevery 60 seconds. - The server fetches posts + comments from Circle Admin API v2 and returns a
SignageSnapshot. - The client keeps a stable, append-only queue of post slides (new posts append to the end of the cycle).
Relevant files:
- Client signage UI:
app/components/SignageClient.tsx - Snapshot fetch + Circle parsing:
app/lib/circle.ts - Posts API route:
app/api/circle/posts/route.ts - Spaces API route:
app/api/circle/spaces/route.ts
- Only post media is shown in the main area.
- Media attached to comments is ignored for the main area.
- Text-only posts render centered on a dark background.
Promo slides are configured per-device on /config (stored in localStorage).
Rotation behavior:
- If there are 0 posts: promos rotate continuously.
- If there are 1–2 posts: promos alternate with posts while cycling through all promo images:
- 1 post:
post, promo1, post, promo2, post, promo3, ... - 2 posts:
post1, promo1, post2, promo2, post1, promo3, ...
- 1 post:
- If there are 3+ posts: promos are inserted after every 3 posts (targeting ~25% promos):
post1, post2, post3, promo1, post4, post5, post6, promo2, ...
Circle videos are commonly delivered as HLS (.m3u8).
- Chrome / Windows: we use
hls.jsto play HLS inside a normal<video>. - Safari: uses native HLS when available.
- Videos autoplay muted (no audio) and do not loop.
- Video slides stay on-screen for the video duration, clamped by per-device config (see “Display timing” below).
- Circle Admin API docs:
https://api.circle.so/ - HLS playback (hls.js):
https://github.com/video-dev/hls.js
The signage page polls /api/circle/posts every 60 seconds, but the API route is configured to be CDN-cached on Vercel to avoid multiplying Circle upstream calls when multiple screens are open.
- API route:
app/api/circle/posts/route.ts - Cache header:
Cache-Control: s-maxage=60, stale-while-revalidate=300 - Daily filtering timezone:
Europe/London
CIRCLE_API_TOKEN: Circle Admin API token. Do not useNEXT_PUBLIC_— this must remain server-only.
CIRCLE_DEFAULT_SPACE_ID: Default CirclespaceIdused when no per-devicespaceIdis selected.NEXT_PUBLIC_SIGNAGE_CONFIG_PIN: PIN required to unlock/configon a device.
The config page (/config) loads a list of Circle spaces from /api/circle/spaces and stores a selected spaceId in localStorage for that browser/device.
When set, the signage client polls /api/circle/posts?spaceId=<id>. This means CDN caching on Vercel will be effectively per-space ID (different query string = different cache key).
The config page also controls how much data we fetch from Circle. These values are stored per-device and are clamped server-side to prevent abuse.
daysBack(0–30): include posts/comments from the last N days in London time.maxPosts(1–500): cap post fetch volume.maxCommentsPerPost(0–25): cap comments per post in the bottom bar.maxTotalComments(0–2000): cap total comments returned.
The config page (/config) also controls display timing stored per-device in localStorage:
commentAdvanceMs: how often the bottom-bar comment rotates.postMinMs: minimum time a text/image post stays on-screen.postMaxMs: maximum time a text/image post stays on-screen.promoSlideMs: how long a promo slide stays on-screen (default 8 seconds).videoMinMs: minimum time a video post stays on-screen.videoMaxMs: maximum time a video post stays on-screen.
Text/image post duration can extend to cycle through comments (up to maxCommentsPerPost) at commentAdvanceMs, capped by postMaxMs.
- Post images use Next.js image optimization on Vercel (remote domains are allowlisted in
next.config.ts). - The client prefetches upcoming slide images (posts, promos, and video posters) to reduce the chance of fading to a slide before its media has started downloading.
- Response header: In Vercel Function logs / your browser devtools, confirm
/api/circle/postsincludes the Cache-Control header above. - Cache behavior: With multiple clients hitting
/api/circle/postswithin 60s, Vercel should serve most responses from cache; Circle should not be hit per viewer. - Daily snapshot: Refreshing the page should render quickly from localStorage if a snapshot exists for today (keyed by
circle:snapshot:YYYY-MM-DD).