Docker production build runs without DATABASE_URL, so any page Next
tries to prerender at build (force-static / revalidate without a
dynamic segment) fails on the Prisma call. Homepage, genre page, and
sitemap previously had page-level revalidate, forcing prerender. Add
force-dynamic to each and move the revalidation inside an
unstable_cache wrapper around the Prisma query — result still cached
5m / 1h at runtime, but build no longer touches the DB.
Detail page (/manga/[slug]) keeps page-level revalidate since its
dynamic segment without generateStaticParams was never prerendered at
build anyway.
Update CLAUDE.md caching section to reflect the new strategy.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stable image URLs (post-signing removal) make page-level caching
safe again. Homepage, genre page, sitemap, and detail page now
revalidate on an interval instead of running Prisma on every hit.
Reader and search keep dynamic rendering (searchParams) but wrap
their Prisma queries in unstable_cache.
TTLs: home/genre/detail 5m, reader manga 5m, reader page meta 1h
(immutable), search 1m, sitemap 1h.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Images now load direct from images.04080616.xyz. Removes read-side
signing (signUrl/signCoverUrls + callers), unlocking browser and
edge caching since URLs are stable. Presigned upload kept for /api/upload.
PageReader retries failed loads via onError as a safety net.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reading progress
- New ReadingProgressButton client component that reads/writes
localStorage key "sunnymh:last-read:{slug}"
- PageReader writes the currently-visible chapter as the user scrolls
through continuous chapters
- Manga detail CTA now reads: "开始阅读" on first visit, or
"继续阅读 · #{N} {title}" when a prior chapter is stored (with
spacing and truncation for long titles)
Multiple genres
- lib/genres.ts with parseGenres() and collectGenres() helpers to
split "冒险, 恋爱, 魔幻" into a list and aggregate across a collection
- Manga detail renders one pill per genre
- GenreTabs filters via parseGenres(...).includes(activeGenre) so a
multi-genre manga appears under each of its genres
- Home / Genre pages compute the tab list from collectGenres(signedManga)
- Card captions (GenreTabs + TrendingCarousel) show "冒险 · 恋爱 · 魔幻"
with truncation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Sign all image URLs server-side with 60s expiry presigned URLs
- Add /api/pages endpoint for batched page fetching (7 per batch)
- PageReader prefetches next batch when user scrolls to 3rd page
- Move chapter count badge outside overflow-hidden for 3D effect
- Fix missing URL signing on search and genre pages
- Extract signCoverUrls helper to reduce duplication
- Clamp API limit param to prevent abuse
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Simple landing page for initial Portainer GitOps deployment.
Includes Dockerfile, docker-compose.yml with pull_policy: build,
and project documentation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>