MoonTV — An Experiment in Vibe Coding
About half a year ago, I started following a project called LibreTV, a video aggregation platform that unifies resources from various scraping sites for centralized search and playback. At the time, I was thinking of setting up a binge-watching platform for my girlfriend, so I kept an eye on this project. Later, I contributed quite a bit of code to it, including replacing the HTML5 player, and gradually became more familiar with its operational logic. However, as I used it more, the project’s shortcomings became apparent: it’s a purely front-end project, with playback records and other data stored in the browser’s localStorage, meaning switching browsers would lose all data; the codebase is entirely in JavaScript and, having passed through multiple hands, became quite messy. Without static type checking, many parts of the code felt risky to touch, making major refactors difficult.
A few months later, a colleague told me that Cursor now supports Alipay payments, which gave me a boost of enthusiasm. Previously, I had been using Trae, whose model capabilities were limited, and during peak times, I had to queue up. Later, it started charging fees, and the free version’s Q&A queues sometimes had hundreds of people, making it almost unusable. So I quickly subscribed to a month of Cursor Pro to give it a try, and decided to rewrite LibreTV with it.
With zero front-end experience, I dove into Vibe Coding, with most of the code being generated directly through conversations with Cursor. The result: MoonTV was born.
Loading repository data...
In just one month, it has already garnered 4.6K stars and 5.4K forks (with some users forking without starring). The star growth trend below clearly shows the strong domestic demand for pirated video content—thanks largely to the various unreasonable moves by platforms like iQiyi, Youku, and Tencent Video, and the Great Firewall standing tall.
The project is built on NextJS and React, supporting deployment on Vercel, Cloudflare Pages, and Docker. It supports three storage methods: localStorage, Redis, and Cloudflare D1. When using Redis or Cloudflare D1, it supports multi-account data isolation and cross-browser data synchronization, along with a convenient management panel. Future plans include integration with Upstash.
This is my first attempt at Vibe Coding, my first front-end/full-stack (the term “full-stack” seems mostly used in the Node/JS ecosystem) project, and my first (relatively serious) open-source project. Thanks to Cursor, most of MoonTV’s development shifted from coding to code review. Whenever I had a requirement, I just chatted with Cursor, and it was done. Of course, there’s an art to how you “chat” with it.
The first challenge in this Vibe Coding experience came right at the start. With no front-end knowledge and only a superficial understanding of popular tech stacks, I wanted to keep up with the times by using NextJS (serverless-friendly) and Tailwind CSS as the core frameworks. So I created a new folder and asked AI to initialize a NextJS + Tailwind project. After half a day of trial and error, the AI-generated projects couldn’t run properly—styles were missing, and pages looked like plain text dumps. After much frustration, I gave up and found a scaffold project on GitHub to use as a base, which finally allowed development to proceed smoothly.
The actual development process went quite smoothly. I mainly used two AI models: Claude Sonnet 4.0 and GPT O3. Their preferences were clear: Sonnet 4.0 was better suited for major changes to the overall project. Sometimes, even for small fixes, it would attempt to refactor the entire project, making it ideal for implementing new features. GPT O3 was better for minor tweaks, keeping changes localized and avoiding chaotic modifications across the codebase, making it more suitable for bug fixes.
Of course, there were scenarios beyond AI’s reach. When implementing the playback page, the state transitions were complex, with many interdependent data points. The AI-generated version ended up with a tangled web of data dependencies. Any data change—like switching episodes or playback sources—could trigger five or six redundant player initializations, causing flickering. No model could fix this, so I had to manually trace the data dependency flow and finally resolved the repeated initialization issue.
Other AI limitations were more niche. For example, most players don’t enable AirPlay or Chromecast casting for m3u8 videos by default, but there are small tricks to force-enable them. Asking AI about this either resulted in a flat “impossible” or incorrect solutions, sometimes even suggesting switching to a different player. After scouring the web, I found this issue in a corner of hls.js’s repo, which helped me successfully enable AirPlay. Currently, AI can only handle more common problems; for niche or even unsolvable human problems, don’t expect AI to help just yet.
The bigger challenge was multi-platform and multi-environment adaptation. The project was planned to support Vercel, Cloudflare Pages, and Docker deployments. NextJS runs natively on Vercel, so no issues there. The project uses a dependency called next-on-pages to translate code for Cloudflare Pages, which occasionally causes compatibility issues. Docker adaptation was even more complex: API handling must run in a Node environment, but NextJS also maintains an edge environment for middleware (like authentication). These two environments don’t share memory, so development requires strict separation of dependencies and available APIs. The client environment is also complicated, mainly due to the WebKit engine: on iOS and iPadOS, all browsers must use WebKit, which differs significantly from Chromium in many APIs and CSS styles, requiring special adaptations.
Since MoonTV originated from LibreTV, initial promotion happened directly in LibreTV’s community groups, even sharing the same discussion group. Active promotion was mostly limited to the linux.do forum, usually just posting changelogs for major updates. Later, many tech bloggers started spontaneously promoting MoonTV: bloggers I follow on X (formerly Twitter), tech channels on Telegram, and even creators on YouTube, Bilibili, and Xiaohongshu began sharing tutorials on how to set up MoonTV.
As the user base grew, issues flooded in. They roughly fell into two categories: Bug Reports—these made up the majority. Who doesn’t write bugs, especially when the code is AI-generated? (Passing the blame!) Feature Requests (FRs) came in two flavors: reasonable requests aimed at improving the project, often about layout, interaction logic, or data integration; and the least welcome—commercialization requests. These came from people hoping to build the project and then monetize it, but lacking the ability to implement such features themselves, they filed FRs instead. These freeloaders naturally want as little work as possible. One guy submitted multiple such issues, got banned, then kept pestering in the Telegram group—truly annoying. Another type was invalid bugs, usually caused by not reading the README, leading to deployment failures or network issues preventing searches, resulting in noisy complaints. Even bystanders in the chat group would roll their eyes and move on when encountering these.
Of course, once a project gains fame, related projects start to emerge. MoonTV has an OrionTV client implementation specifically for Android TV, which uses MoonTV as its backend and syncs playback records. Community members have also developed Android and iOS versions, though the iOS version can’t be published due to various restrictions.
Loading repository data...
That’s all for now. I originally planned to finish this article in July, when the star count was just 2K, but kept delaying—writing articles is never as fun as coding. That’s just how I am: once I have something on my plate, I can’t stop thinking about it, whether eating or sleeping. Getting up at 2 or 3 a.m. to fix a bug is common, and for two weeks straight, I averaged just over six hours of sleep a night—truly working day and night.