Sitnice Bitnice
A full-stack bilingual (Croatian / English) recipe and editorial platform, built solo end-to-end: a legacy WordPress site rebuilt on Payload CMS 3 + Next.js 15, with an automated WordPress-to-headless migration pipeline, anonymous IP-hashed ratings, three-tier RBAC, and a multi-layer caching strategy on Vercel and Supabase.
- Role
- Sole full-stack developer
- Timeframe
- 2026 – Present
- Stack
- Next.js 15 · React 19 · Payload CMS 3 · TypeScript · PostgreSQL · Supabase · Drizzle ORM · Tailwind CSS · shadcn/ui · next-intl · Lexical · Resend · Vercel · pnpm

A production-grade, bilingual (Croatian / English) recipe blog and editorial platform built on Payload CMS 3 + Next.js 15, migrated from a legacy WordPress site and deployed on Vercel with Supabase infrastructure. Designed, built, and operated end-to-end as the sole full-stack developer — architecture, backend, frontend, CMS configuration, content migration, deployment, and production operations.
Key Features Built
Full-stack bilingual CMS platform
- Architected a bilingual (Croatian / English) recipe and editorial platform on Payload CMS 3 + Next.js 15, with localized content across all collections — titles, descriptions, ingredients, steps, slugs, rich text, and SEO metadata (default locale Croatian, English fallback).
- Implemented locale-aware routing with culturally appropriate URL paths —
/recepti(recipes) and/objave(posts) — using next-intl withlocalePrefix: 'never'(state-based locale rather than URL-prefixed) plus permanent redirects for legacy English paths.
WordPress-to-Payload migration pipeline
- Built a complete WXR (WordPress eXtended RSS) parser and importer that extracts WPRM (WP Recipe Maker) recipe data from PHP-serialized post meta, with a JSON fallback.
- Automated media migration: downloads images from WordPress and re-uploads them to Supabase S3, linking them to Payload documents — including a dedicated step-image scraper for per-instruction images.
- Implemented dry-run mode and skip-by-slug idempotency for safe, repeatable migrations; content imports as drafts by default with flags for
--publish,--skip-images,--author, and--cleanup.
Recipe content system with structured data
- Designed a rich recipe schema — ingredients with a three-mode unit system (metric / imperial / general), step-by-step instructions with per-step images, cooking time, servings, and difficulty.
- Built smart client-side ingredient scaling that adjusts quantities to the reader's selected serving size and auto-converts units across thresholds (ml↔l, g↔kg, tsp↔tbsp), with tap-to-check-off strikethrough — all with no server round-trip.
- Implemented Schema.org
RecipeJSON-LD withaggregateRating,recipeIngredient, andHowToStepinstructions for search-engine rich results.
Anonymous star rating system
- Built a custom rating API (
POST /api/ratings) with SHA-256 IP hashing (salted withPAYLOAD_SECRET) to enforce anonymous, one-vote-per-IP-per-recipe (returns HTTP 409 on repeat votes). - An
afterChangehook recalculates the recipe'saverageRatingandratingCounton every new vote; page freshness is handled separately via on-demand ISR revalidation so ratings appear without a redeploy.
Role-based access control (RBAC)
- Implemented a three-tier access model (admin / editor / user) with function-based Payload access policies.
- Granular permissions: editors can create and update but not delete; admin-only collections are hidden from the sidebar for non-admins; the public ratings endpoint is open for creation but locked for updates.
- Extended editor access to forms and form submissions, with dedicated editor dashboard cards, so non-admin contributors can manage collaboration inquiries.
Draft/publish workflow with live preview
- Enabled autosaving drafts (2-second interval, 25 versions per document) across Recipes, Posts, and Pages.
- Configured live preview with three responsive breakpoints (mobile 375, tablet 768, desktop 1440) and a
LivePreviewListenerfor real-time content refresh in the frontend.
Search & filtering with infinite scroll
- Configured
@payloadcms/plugin-searchwith custombeforeSynchooks that normalize slugs, SEO metadata, and categories into a flat search index. - Built recipe filtering with Next.js Server Actions (query, difficulty, category), rendered in an infinite-scroll grid.
Transactional email & self-service auth
- Integrated Resend for transactional email, including a fully custom Croatian-language HTML password-reset template and self-service forgot-password token flow in the Users collection.
- Extended the form builder's
beforeEmailhook to parse comma-separated / JSON recipient lists forto/cc/bcc, and to build a fallback HTML table from raw submission data when the rendered body is empty — so no inquiry is ever lost.
CMS-managed forms & collaboration page
- Built an idempotent
setupCollabseeding endpoint (POST /api/setup-collab, admin-only) that programmatically creates a fully localized HR + EN collaboration form and page, carefully preserving Croatian-localized fields when seeding the English layout. - Integrated
@payloadcms/plugin-form-builderwith react-hook-form for client-side validation and Lexical rich-text confirmation messages.
Multi-layer caching strategy
- Implemented ISR with on-demand
revalidatePathfrom collection hooks,unstable_cachewith tags for globals, andReact.cachefor request-level deduplication. - Built a Vercel Cron warmup endpoint (
/api/warmup, every 8 minutes) that pings Payload/Postgres to keep serverless functions and the Supabase connection pool warm.
S3 media pipeline with filename sanitization
- Configured Supabase Storage as an S3-compatible backend with public CDN URLs and Payload access control disabled for direct serving.
- Built a
sanitizeMediaFilenamehook that transliterates Croatian diacritics (č→c, š→s, đ→dj), strips combining marks and non-ASCII characters, and normalizes spaces — since S3 rejects non-ASCII object keys.
Dark mode & print-optimized layout
- Implemented theme switching via a
data-themeattribute withlocalStoragepersistence and system-preference detection, plus flash-prevention. - Built print CSS (A4
@page) that reformats recipe pages into a compact two-column ingredients + steps layout and hides all interactive chrome.
Architecture & Patterns
Co-located CMS + frontend
Payload CMS 3 runs inside the same Next.js process — no separate API server. The admin panel lives under /(payload)/admin, the auto-generated REST/GraphQL APIs under /(payload)/api, and the public frontend under /(frontend)/[locale].
Reusable block system
Content blocks (Archive, Banner, CallToAction, Code, Content, Form, Media, RelatedPosts, RelatedRecipes) follow a config.ts / Component.tsx / Component.client.tsx split, dispatched via a RenderBlocks mapper. Blocks can be embedded inline within Lexical rich text via BlocksFeature.
Custom admin UI
- SlugComponent — a custom field with a lock/unlock toggle for auto-formatting vs. manual slugs.
- BeforeLogin / AfterDashboard / AdminNav / AdminLogo / AdminIcon — branded admin customizations, including role-aware dashboard cards.
- Bilingual admin labels via a custom
t()helper that generates{ en, hr }objects from message files.
Hierarchical categories
@payloadcms/plugin-nested-docs provides tree-structured categories with breadcrumbs, shared by posts and recipes.
Database connection management
The Postgres pool is tuned for Supabase's Supavisor pooler: max: 5 connections (sized for concurrent generateStaticParams at build time), 30s connect timeout, and a 20s idle timeout that closes connections before the pooler drops them.
Infrastructure & DevOps
Deployment
- Vercel (Frankfurt
fra1region) with ISR, Server Actions, and Cron Jobs. - Supabase for PostgreSQL (Supavisor transaction pooler) and S3-compatible object storage.
- Resend for transactional email delivery.
CI/CD
- GitHub Actions — an automated weekly workflow that syncs from an upstream template repo, merges with an
oursconflict strategy, and auto-opens a PR intomain(peter-evans/create-pull-request).
Monitoring & reliability
/api/health— lightweight status endpoint for external monitoring./api/warmup— Vercel Cron (every 8 min) to prevent cold starts and pool timeouts.- On-demand ISR revalidation from CMS hooks keeps published content fresh without redeploys.
Project Ownership Highlights
- Full project ownership — sole developer across architecture, backend, frontend, CMS configuration, content migration, deployment, and ongoing production operations.
- Internationalization depth — end-to-end bilingual support spanning content, UI, admin panel, SEO, URL routing, and email templates.
- Legacy system migration — planned and executed a WordPress-to-headless-CMS migration with automated data extraction from PHP-serialized meta, media re-hosting, and idempotent, resumable imports.
- Production operations — connection-pool tuning, cold-start mitigation, CDN-optimized media delivery, and a layered caching strategy.
- User-experience focus — live preview for editors, smart ingredient scaling for readers, responsive design, dark mode, and print-optimized recipes.