Work / Web app · 2026
VidzyPlayer
VidzyPlayer is a False Logics product for hosting and embedding marketing videos. I built all of it: a NestJS and PostgreSQL API, a Next.js dashboard, a Vite embed app and a shared React player, plus Paddle billing and the content pipeline behind the blog. It is live.
- case study
- live
- own product
- web app
- Type
- Web app · SaaS: marketing video hosting
- Built with
- TypeScript, NestJS, Drizzle ORM, PostgreSQL, Next.js, React, Vite, pnpm workspaces, Paddle Billing, Bunny CDN, FFmpeg, PHP, WordPress, Node.js, pm2, Vercel
- Also uses
- Jest
- Last active
- 2026
Overview
VidzyPlayer lets marketers host and embed videos from YouTube, Vimeo or file uploads, and turn viewers into leads with branding presets, timed calls to action, popups, lead-capture gates, password protection, playback analytics and email preview GIFs.
It is my own product, run under my company False Logics, and it is live.
Context and problem
The product appears to target marketers who need more from an embedded video than a plain YouTube or Vimeo player gives them: their own branding, a way to ask for action, gated content and engagement data.
My role
I am the founder and the only developer. Every commit in the core repository is mine, covering the API, the dashboard, the player, billing and deployment.
I also run the editorial workspace that publishes the vidzyplayer.com blog. Its git history only starts in October 2026, so it understates the work done before then.
Solution and user experience
A marketer adds a video, styles the player with a branding preset, and attaches timed CTAs, popups or a lead-capture gate; the dashboard then lists captured leads and shows how each video performs.
The video is published through a white-label embed at /embed/:videoId, which can hide the player controls entirely.
Key features
- Engagement: timed in-video CTAs, popups and lead-capture gates, with submissions listed in the dashboard.
- Analytics: playback events are tracked and shown per video and across the workspace.
- Library: password-protected videos, custom thumbnails and branding presets.
- Email preview GIFs: a 5-second, 480 px, 12 fps clip of the video for use in emails, with thumbnail fallbacks for YouTube and Vimeo.
- Checkout: a WordPress shortcode starts a Paddle purchase; the webhook creates the licence and purchase records and sends a set-password onboarding email.
- Admin: users, licence codes and SMTP settings.
- Content operations: the blog runs on a pipeline from SEO brief to human-approved WordPress post. A Search Console refresh queue ranks the published articles worth updating, and a draft-only builder turns feature-page specifications into WordPress pages with FAQ schema.
Architecture and stack
The system is split into four units: a NestJS API on PostgreSQL through Drizzle, a Next.js dashboard, a static Vite embed app, and a shared @vidzy/player React package.
- API: NestJS 11 modules, controllers and guards (JWT auth and an admin guard), written in TypeScript.
- Data: PostgreSQL with a Drizzle schema and migrations covering users, videos, events, gates and submissions, popups, CTAs, licences and Paddle purchases.
- Dashboard: Next.js 16 App Router with React 19, Radix-based UI, react-hook-form and Recharts.
- Player: one React package with YouTube and Vimeo providers and an event hook, used by both the dashboard preview and the public embed.
- Media: uploads go to Bunny CDN storage; preview GIFs are cut with FFmpeg and yt-dlp.
- Plans: plan definitions live in one shared package that both the API and the UI import.
- Deployment: the API runs under pm2 on a DigitalOcean droplet with a Neon Postgres database, and the embed app deploys to Vercel.
Implementation details
Billing writes a purchase intent before the customer reaches Paddle, and stores every Paddle event idempotently, so a repeated webhook cannot create a second licence.
The checkout plugin talks to the API through a public WordPress REST route rather than admin-ajax.
Preview GIFs run in a bounded in-process queue with at most two jobs at a time; on start-up the service re-enqueues anything still pending.
Billing was built on feature branches and merged through two pull requests; the second one fixed the issues raised in an AI code review.
Challenges
- Paddle signs its webhooks over the raw request body, so the NestJS app had to keep the raw body available for signature verification.
- Preview GIF jobs could get stuck in pending; I fixed the queue and added a Vimeo thumbnail fallback.
- Downloading a section of a video needed yt-dlp pointed at the bundled FFmpeg binary.
Trade-offs
Keeping the GIF queue in process likely keeps operations simple, with no Redis to run, at the cost of losing in-flight jobs on a restart; re-enqueueing pending rows on boot covers that gap.
Screenshots
Links
Skills demonstrated
- Full-stack SaaS development across an API, a dashboard, an embed app and a shared package.
- REST API design with NestJS, and relational data modelling with PostgreSQL and Drizzle.
- Payment integration: Paddle webhooks with signature checks and idempotent event storage.
- Media processing with FFmpeg.
- WordPress plugin development in PHP.

