Web app · case study
Next-movies
Movie and TV browser running as a Cloudflare Worker, with rate limiting and IP bans in front of the TMDB API.

- My role
Sole developer
- Type
Web application
- Stack
Next.js, TypeScript, Tailwind CSS, Redis
- Runs on
Cloudflare Workers, no origin server
- Edge
Cloudflare, custom domain on the Worker
Why I built it
I built Next-movies because I watch a lot of movies and wanted my own website for looking up films, shows and actors the way I like, instead of searching the web every time.It was my first Next.js project, built while I was learning the framework.It has since become where I practise deployment: it moved from Vercel to a VPS, and now runs as a Cloudflare Worker with no origin server.
The problem
My own website for finding movies, TV shows and actors, with the filters and layout I want.
My first Next.js app, used to learn the framework.
Keep the TMDB token secret and the free API quota safe from scrapers, at no hosting cost.
Features
- Infinite scrolling
- In-app trailer playback
Architecture
- Browser to Middleware: HTTPS
- Browser to Static assets: /_next/static
- Browser to TMDB images, YouTube: direct
- Middleware to Upstash Redis: limits, bans
- Middleware to Next.js 15
- Next.js 15 to Upstash Redis: list cache 300s
- Next.js 15 to Incremental cache: revalidate 600s
- Next.js 15 to TMDB API: bearer token
In words
The app runs as a Cloudflare Worker via OpenNext, with no origin server.Middleware blocks script user agents, checks the IP ban list, and rate-limits through Upstash Redis.Only then does the TMDB proxy run.The TMDB token is a Worker secret and never reaches the browser.Fetch results persist in a KV cache behind the regional Cache API.Static assets come from the Worker's assets binding; images load straight from TMDB's CDN.
Pipeline
push → main
Workers Builds
Cloudflare's own CI
opennextjs-cloudflare build
Next.js → Worker bundle
Deploy
new Worker version, atomic
Decisions
- Cloudflare Workers, after Vercel and a VPS.
It moved from Vercel to a hardened VPS (Docker, nginx), then to Workers via OpenNext.The VPS setup was deleted after.
- The Cache API in front of the KV cache.
Renders reached 50% of the daily KV operations cap.Cache API reads are unmetered, so repeat reads skip KV.
- Upstash Redis for rate limits and bans.
A Worker runs as many instances.Only a shared store gives one global limit.A strict in-memory limit is the fallback.
- Trust only the CF-Connecting-IP header.
Cloudflare sets it; clients can't forge it.X-Forwarded-For is never trusted.
- Images load straight from TMDB's CDN.
No image-optimization worker to run.No image proxy route to expose.
Challenges
- Free-tier limits at the edge.
The Cache API layer cut KV usage without a paid plan.
- Moving a Next.js app onto the Workers runtime.
OpenNext handles the build.The health check was rewritten for the Workers runtime.
Testing
A QA script checks function, security, performance and edge cases on a running instance.
A validator tests the rate limiter in memory or against Upstash.
Next.js and sharp advisories are patched as they appear.
Not yet: either script in CI.
Outcome
Live at nextmovies.wyco-dev.com, served from Cloudflare's edge.
Next
Run the QA script after every deploy
Accounts and watchlists
Stack
- Next.js
Runs as a Cloudflare Worker via OpenNext.Renders pages and proxies the movie API.
- TypeScript
Types the movie, show, and actor payloads.
- Tailwind CSS
Styles the browse and detail views.
- Redis
Rate limits, IP bans and list caching, on Upstash.
Security
- Authentication
None.Public read-only app.
- Input validation
Allow-listed API keys.Every query parameter is validated.
- Protections
The API proxy keeps the token server-side.Rate limits, IP bans and security headers apply.
Operations
- Runs on
Cloudflare Workers, no origin server
- Edge
Cloudflare, custom domain on the Worker
- Deploy
Push to main triggers Workers Builds.It runs the OpenNext build and ships a new Worker version.The TMDB token is a Worker secret.
- Health check
Middleware returns 403 to scripts and 200 to browsers.A 403 to a script means the filter works.