🎬 Reely#
Discover, track, and stream movies and TV shows — a fast, modern, TMDB-powered viewing experience.
🚀 Watch on Reely Space →#
Browse trending titles, dive into rich detail pages, and pick up exactly where you left off — all from a single keyboard-friendly UI.
Reely is a production-grade movie and TV show tracker built on the TMDB API. It is designed to feel native: prerendered to static assets for speed, animated for delight, and instrumented for serious frontend craftsmanship.
⭐ If Reely is useful to you, please star the repo.
✨ Features#
- TMDB-powered catalog — trending, popular, top-rated, now-playing, and on-the-air movies and TV shows, pulled live from the TMDB API.
- Animated hero slider — auto-rotating showcase of trending media on the landing page, with a muted trailer that plays inline while a slide holds the frame. Autoplay is opt-out via an on-slide toggle, and starts switched off on low-power devices so a weak phone isn't handed a video player and a full-screen Ken Burns at once. Your explicit choice always overrides that default.
- Browse movies & TV — dedicated listing pages with a deep, live-applying filter system and infinite scroll (TanStack
useInfiniteQuery+ intersection observer). - Advanced filters — sort, tri-state genre pills (include / exclude / off), TMDB rating, vote count, runtime, release year (decade chips + range slider), original language, age rating (certification), and where to watch (streaming-provider logo grid + region picker). All filters live-apply on a 300ms debounce — no "Save" button — with an active-filter chips bar (tap to remove, or clear all) above the grid.
- Rich detail pages — synopsis, cast, similar titles, recommendations, trailer, and ratings — fetched in a single TMDB
append_to_responsecall. - In-page streaming player — watch movies and individual TV episodes via a configurable external source (see the in-app disclaimer).
- Season & episode navigator — drill into any TV show by season and episode with a dedicated selector.
- Trailers — YouTube trailer dialog plus a hero trailer preview.
- Collections — franchise / collection pages that group a series of films.
- Command-palette search (⌘K) — instant, debounced search across movies and TV via a
cmdkdialog, with All / Movies / TV chips and remembered recent searches. No page reload. - Genre pages & chips — jump straight into any genre for movies or TV.
- Real IMDb ratings (opt-in) — genuine IMDb scores layered over TMDB, served from prebuilt static shards (
pnpm imdb:ratings) with a TMDB fallback. BehindNEXT_PUBLIC_IMDB_RATINGSand off by default: enriching list rows costs one TMDB subrequest per item, which overruns the Workers free-plan 50-subrequests-per-invocation cap. - Watchlist — save titles you want to watch, kept on your device (no account required).
- Watch history — track watched movies and episodes, remove single items, or clear everything, on a dedicated page.
- URL-synced filters — every active filter lives in the URL via
nuqs, so filtered views are shareable and back-button friendly (transient UI chrome like open accordions stays out of the URL, keeping shared links clean). - Web Share — share any title through the native share sheet.
- Privacy-first — watchlist, history, and recent searches live entirely in your browser; there's no login and nothing is stored on a server.
- SEO & structured data — full JSON-LD (Website, Organization, CollectionPage, Breadcrumb), Open Graph, Twitter Cards, dynamic OG image generation, sitemap, and robots.
- Accessible & responsive — skip-to-content, aria roles, a dedicated mobile nav, and a dark aurora UI that scales cleanly to phones.
- Optimized imagery — ImageKit CDN with an automatic
wsrv.nl→ TMDB origin fallback chain, driven by a customnext/imageloader so every image still ships a realsrcsetand each device downloads the size it actually renders. - Installable PWA — see the Progressive Web App section below.
- Analytics built in — PostHog product analytics, client-side only, with every event centralized in
lib/analytics.tsand build-time source-map upload so minified production stack traces symbolicate. - Deployed on the edge — see Architecture below.
📲 Progressive Web App#
Reely is a fully installable PWA — not just a manifest, but a working offline-resilient app shell.
- Installable — "Add to Home Screen" on Android/Chromium (native
beforeinstallpromptnudge) and iOS (Share → Add to Home Screen hint). Runs standalone, portrait, with a black theme. - App shortcuts — long-press the icon for Browse Movies and Browse TV Shows jump links.
- Offline-resilient — a hand-written service worker (
public/sw.js, nonext-pwa/serwist/Workbox) with a per-request caching strategy:- immutable
/_next/static/*assets → cache-first - page navigations → network-first, falling back to the last cached page, then a custom
/offline.html - icons, manifest, and images → stale-while-revalidate
- never cached: the streaming player,
/api/*, RSC payloads, and any cross-origin request
- immutable
- Native touches — maskable icons (192 / 512), Apple touch icon, theme colors,
browserconfig.xml/ mstile, and a Safari pinned-tab icon.
Streamed video is live third-party content and is intentionally never cached for offline playback.
🏗️ Architecture#
Reely ships as a Next.js static export (output: 'export') on Cloudflare Workers Static Assets, plus one hand-written Worker for the parts that cannot be static. Next.js does not run in production at all.
This replaced an OpenNext deployment. Under OpenNext, 20–46% of Worker invocations were being killed on the free plan's 10ms CPU budget: any detail id outside the prerendered set re-rendered React on every request and could never become a cache hit, because Cloudflare will not edge-cache Worker-generated HTML. After the migration that figure is 0.0%, and p99 CPU went from ~700ms to ~8ms.
- Static assets match before the Worker runs. Every prerendered page is a plain file: zero CPU, and it doesn't count against the invocations cap.
- The Worker handles only what can't be static —
/api/*(search, filtering, infinite scroll, season details, watch providers) and detail ids outside the prerendered set. A tail id costs one TMDB fetch plus anHTMLRewriterpass that injects the real title, OG/Twitter tags, JSON-LD, and a crawlable<h1>, so crawlers and unfurlers can't tell it from a prerendered page. - No Server Actions. A static export can't contain them; the browser talks to the Worker over HTTP instead, and the Worker calls the very same
services/*functions the build calls. caches.defaultis keyed by URL + Next build id, because cached fallback HTML references content-hashed chunks that the next deploy deletes.- The real ceiling on site size is the 20,000-file asset cap — a static export writes ~10 files per route, so route depth is tuned in
lib/media-page.ts.
Detail pages use TMDB append_to_response so a whole page renders on one TMDB request, and every TMDB call goes through a single governed client (lib/fetch-client.ts) with a concurrency cap and 429 backoff.
🛠️ Tech Stack#
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, Turbopack, RSC, static export) + React 19 |
| Language | TypeScript 6 |
| Styling | Tailwind CSS 4 + tailwindcss-animate + @tailwindcss/typography |
| UI | shadcn/ui + Radix UI, lucide-react icons, Framer Motion, sonner toasts |
| Data fetching | TanStack Query 5 + React Server Components |
| URL state | nuqs |
| Search | cmdk command palette + use-debounce |
| Infinite scroll | react-intersection-observer |
| PWA | Web App Manifest + hand-written service worker (no next-pwa/serwist) |
| Data source | TMDB API + optional IMDb ratings + ImageKit imagery |
| Streaming | External, configurable video source |
| Analytics | PostHog (client-side only) |
| Deployment | Static export on Cloudflare Workers Static Assets + one hand-written Worker |
| Tooling | ESLint, Prettier, Husky, Commitlint, Renovate |
🙌 Credits#
Built by Mohamed Gado. Data and imagery courtesy of TMDB — Reely is not endorsed or certified by TMDB.

