< Back to projects

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.

nextMovie preview
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

Architecture diagramThe 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.CLOUDFLARE WORKERTMDB images, YouTubeloaded by the browserallow-listed in CSPBrowserinfinite scrolltrailer playerMiddlewareblocks script UAsIP bans, rate limitsNext.js 15via OpenNext/api/tmdb allow-listStatic assetsASSETS bindingcontent-hashedIncremental cacheregional Cache APIbacked by KVUpstash Redis120/min API, 40/min search24h ban after 15 strikesTMDB APItoken is a Worker secretHTTPS/_next/staticdirectlimits, banslist cache 300srevalidate 600sbearer token
Dev · applicationSec · security controlOps · infrastructureExternal · third partyScroll sideways to see all of it.
  • 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

  1. Workers Builds

    Cloudflare's own CI

  2. opennextjs-cloudflare build

    Next.js → Worker bundle

  3. 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.