Skip to content
<k/>
0%
Loading assets · 0s
<k/>
Loading...

Project

Kokunime

An anime download site built with Next.js. Pure download links, no streaming. Left untouched for almost three years, then revived and overhauled with vibe coding in a single sprint.

Shipped2023
Stack
next-jsreact-19typescripttailwind-css-v4cheerio
Kokunime

I built Kokunime in June 2023. The goal was simple: scrape an upstream anime site and surface the download links in a cleaner interface. No streaming player, no account system. Just an anime list, detail pages with per-resolution download links, search, and genre/season browsing.

The early commits are in Indonesian. After the initial build, I mostly just kept updating dependencies. For almost three years, nothing meaningful happened.

Then in August 2026 I came back to it.

Kokunime Home Page Kokunime Detail Page

🤖 The Vibe Coding Revival

I revived the project with DeepSeek V4 Flash as the primary driver. The prompting style was casual. I described a feature, the model generated it, and I kept sending it back until it looked right. No upfront architecture plan, no design doc.

The first sprint upgraded the stack to Next.js 16, React 19.2, and Tailwind v4, redesigned the UI from scratch with a dark mode, replaced the API-based data layer with a direct scraper, and refactored the file structure so every file stayed under 100 lines.

After that, I kept finding things to fix. The vibe coding approach meant issues came up in batches, so I worked through them in numbered rounds: LCP fixes, security headers, CDN caching, iOS UX, accessibility, pagination bugs. By the end there were 24 rounds of fixes on top of the initial sprint.

Genres and Seasons Browsing

🏗️ Architecture

The site is a Next.js app with no public API routes. Server components call the scraper layer directly, which fetches upstream HTML via axios and parses it with cheerio. Results are cached with unstable_cache at a 15–60 minute TTL depending on the route.

  • Home and listing pages: ISR with 15-minute revalidation
  • Anime detail pages: 100 pages prerendered via generateStaticParams, the rest handled on-demand
  • No /api/* routes: the scraper only runs server-side, nothing is exposed to the client

One edge case I had to handle was redirect posts on the upstream site. Some entries have been moved and only contain a link in the section title, not in the usual download blocks. If you drop those groups silently, the whole post loses all its download links. I catch them by checking if the download block comes back empty and surfacing the redirect link instead.

🔒 Security

Scraping upstream HTML means I can't trust any of it. Two things I did to reduce the risk:

  • URL validation: all scraped hrefs are checked against ^https?:// before being rendered as links. This blocks javascript: URLs from landing on the page as clickable anchors.
  • Security headers: X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Strict-Transport-Security, and a partial CSP covering <base> hijacking, object injection, and form exfiltration. Full script-src CSP isn't practical with Next.js because the inline theme script and RSC payloads require unsafe-inline.

⚡ Performance

Most of the 24 fix rounds were performance issues that showed up after the initial vibe coding sprint.

The CDN caching was the trickiest. Dynamic SSR routes in Next.js default to Cache-Control: no-store, which means the browser can't use the back/forward cache, so navigating back from a detail page triggered a full reload. I fixed it with max-age=0, s-maxage=900, stale-while-revalidate=900: browsers revalidate every visit so bfcache still works, and the CDN edge holds the page for 15 minutes.

The genres and seasons pages paginate via ?page=N. Netlify's default Netlify-Vary header doesn't include page, so every page number was sharing the same edge cache key and all of them served page 1's HTML. I extended the vary header to include page explicitly.

A few other things that came up:

  • The hero image was wrapped in an animation component, which delayed LCP. I removed the wrapper and set fetchpriority=high directly on the image.
  • CSS transitions for the theme toggle caused GPU artifacts on Safari and Firefox. I replaced them with the native View Transitions API.
  • Heavy features are lazy-loaded so they don't land in the initial bundle.

🐣 What It Is

This is a playground project. The vibe coding approach got features done fast, but it also meant I spent a lot of time in fix rounds cleaning up what the prompts got wrong. That tradeoff felt fine here. It's a download site, not a production system. It's been a useful place to work through scraping patterns, CDN quirks, and Next.js caching behavior on a real workload.

Comments