A minimal Next.js + Tailwind CSS starter demonstrating an embedded video player (Cloudflare Media Delivery iframe) and a layout ready for adding a paywall or access controls.
This README documents how to run, build, and extend the project, and explains the repository layout and key implementation choices.
This repository is a focused proof-of-concept and starter kit for building a video-paywall web application. The current codebase intentionally keeps the surface area small: it provides a clean Next.js 14 app-router layout and a homepage that renders a full-viewport iframe player coming from a media delivery CDN.
Primary goals:
- Provide a minimal, production-friendly UI surface for embedding a remote video player while keeping client-side code simple and auditable.
- Serve as a scaffold for implementing secure playback gating patterns (for example: server-side token signing, entitlement checks, and payment integrations).
- Make it easy to iterate on the paywall UX without worrying about the player internals: the player is embedded via an iframe, so DRM and playback complexity can remain with your CDN/provider.
Intended audience:
- Developers building a paid video product (pay-per-view, subscription video-on-demand, educational platforms).
- Teams who want a quick starting point that separates playback (CDN/player) from access control (their app and server).
Common real-world use cases:
- Gate a course video so only purchased students can play it.
- Provide short previews publicly and paid playback for full videos.
- Use short-lived signed playback URLs to protect content from hotlinking or unauthorized re-use.
-
Next.js 14 App Router
- Why it matters: Server components and server-first patterns simplify secure server-side token creation and session-aware rendering.
- Where:
app/layout.tsxsets global layout and font;app/page.tsxis the homepage.
-
Full-viewport embedded player (iframe)
- What it does: Renders an iframe that loads a remote player URL (
https://iframe.mediadelivery.net/play/...). - Advantages: Keeps playback sandboxed. Upgrades to DRM/CDN features don't require frontend rebuilds—only the iframe
srcor tokenization logic. - How to customize: edit
app/page.tsxand replace the iframesrc. See "Where to edit the code" below for exact location.
- What it does: Renders an iframe that loads a remote player URL (
-
Minimal styling foundation with Tailwind CSS
- Why: Fast UI iterations, predictable utility classes, and a small CSS surface.
- Included:
tailwind.config.ts,postcss.config.mjs,app/globals.css.
-
Small utility library (
lib/utils.ts)- Purpose: Provide helpers like
cn(className composer) to keep components clean and composable.
- Purpose: Provide helpers like
-
Clean dependency set and scripts
- Scripts available:
dev,build,start, andlint(seepackage.json). - Lightweight dependencies to make integration with auth and payments easy.
- Scripts available:
-
Extension-ready architecture
- Common extensions are straightforward: server token endpoints, payment webhooks, analytics, and per-user entitlements.
-
Inputs
- Optional: authenticated user context, e.g. session cookie or JWT.
- Optional: server-signed playback token (string) or signed iframe URL.
-
Outputs
- Renders: an iframe with the playback surface when access is allowed.
- Renders: paywall/login CTA when access is denied.
- Emits: (extension) playback-start events to analytics.
-
Error modes
- Invalid token or missing entitlement: Do not render the iframe; show explanatory UI.
- CDN unreachable or cross-origin blocking: show fallback UI and retry instructions.
-
Success criteria
- Authorized user sees playable video in iframe.
- Unauthorized user sees clear path to purchase or sign in.
- Expired or invalid tokens: detect and prompt re-authentication or token refresh.
- Cross-origin embed restrictions: some CDNs or browser policies may block embedding—provide a fallback experience.
- Network slowness: show loading skeletons and retry controls.
- Browser autoplay restrictions: ensure player configuration and user gestures are handled gracefully.
- Never expose private signing keys, CDN API keys, or secret tokens in client-side code. Generate playback tokens server-side.
- Use HTTPS for all endpoints and mark cookies with appropriate flags (Secure, HttpOnly, SameSite) as required.
- When integrating payments, follow PCI/DSS best practices and store the minimum necessary PII.
- Consider CSP and
iframesandbox attributes if you need additional restrictions on embedded players.
- Change the player URL:
app/page.tsx→ update iframesrc. - Change layout/global styles:
app/layout.tsxandapp/globals.css. - Add server endpoints and token signing: create API routes under
app/api/(for exampleapp/api/token/route.ts). - Add auth helpers: new files under
lib/(for examplelib/auth.ts) or integrate third-party auth (NextAuth, Clerk).
-
Quick mock paywall (no external services):
- Add a small API route that returns
{ authorized: boolean }based on a mocked header. - On page load, call the endpoint and only render the iframe when authorized.
- Add a small API route that returns
-
Secure paywall (recommended production flow):
- Implement authentication and user/session storage.
- Create a server endpoint that validates entitlement and requests a signed playback token from your CDN.
- Return the signed URL to the client and render the iframe with that URL.
-
Payments & entitlements:
- Integrate Stripe Checkout or a similar provider.
- Use server webhooks to update user entitlements in a DB and allow the token endpoint to return signed URLs only to entitled users.
- If the page is blank or Next.js errors occur: ensure
npm installwas run and the Node.js version is compatible. - If the iframe does not load: check browser console for blocked requests and verify the external URL is reachable.
- For token-related playback failures: confirm server-side signing and token TTL match the CDN's expectations.
If you want, I will now update README.md to add any example flows you prefer (mock paywall, server token endpoint design, or a sample sequence diagram). Tell me which flow you want documented and I will add it to the README (no code changes unless you request them).