Case study01 / 06

Dinastia

Pedigree-pet marketplace for Moldova · RO/RU

My own product. I made everything from the name and logo to the database. It’s a bilingual catalog where breeders and owners list pedigree dogs and cats, and buyers send a request straight from the pet’s page.

Role
Founder · design and full-stack
Period
Aug 2026 – present
Context
Own product
Format
Solo · Chișinău
  • Next.js App Router
  • React 19
  • Supabase (Postgres, Auth, Storage)
  • Row Level Security
  • next/image
  • Resend
  • Telegram Bot API
  • Vercel
  • node:test
Open sitedinastia.mdCode available on request
Dinastia home page on a desktop screen
Dinastia home page on a phone

The task

Pedigree puppies and kittens in Moldova are mostly sold through scattered classifieds and social media, where documents and sellers are hard to check. I wanted a catalog people can trust. It had to rank in Google for breed and city searches and work in Romanian and Russian. Breeders had to manage their own listings, but without a way to skip moderation.

  • Fast, indexable pages for every breed and city
  • Self-service breeder accounts, with moderation before anything goes public
  • Requests that reach a person right away, because the market runs on phone calls
  • Personal data such as document scans and phone numbers kept out of the public HTML

What I built

Catalog and SEO

  • Next.js App Router with /ro and /ru path prefixes. One dynamic segment serves both breed and city landing pages (11 dog breeds, 7 cat breeds, city landing pages).
  • Static generation with generateStaticParams and hourly ISR, about 90 static pages per build.
  • All schema.org markup comes from one helper. A pet gets Product + Offer and a breed page gets AggregateOffer. There are also BreadcrumbList, ItemList, FAQPage and BlogPosting, plus Organization or Person for each seller.
  • Sitemap with real lastmod dates and RU/RO hreflang. Thin listings, with no photos or a short description, are left out.
  • Middleware that redirects once with a 308 to the final URL, with no redirect chains.

Breeder dashboard and admin

  • Listing editor with a draft → pending → published / rejected / archived flow. Rejection reasons are shown in the dashboard.
  • Photos are compressed in the browser before upload. They are resized on a canvas and saved as WebP with a JPEG fallback, 200 KB at most.
  • Moderation console covering statuses, document checks through short-lived signed links, sellers, site settings and stats.
  • Tag-based cache revalidation after moderation, and a daily cron that removes accounts once the deletion grace period ends.

Requests and contacts

  • /api/lead validates input, has a honeypot field and allows 5 requests per hour per IP. It looks up the pet and seller on the server, then posts the request to Telegram, writes it to the lead log and notifies the breeder.
  • The seller’s phone number is not in the HTML. It is fetched when the buyer clicks, through /api/contact, limited to 20 requests per 10 minutes per IP.
  • Rate limits are counted in Postgres with one atomic upsert, so they hold across serverless instances.

Outcome

organic Google clicks in the first 2 months (Search Console)
~500
in Google for “buy pedigree cat / pet” in Moldova (Oct 2026)
Top 3–4
statically generated pages
~90
languages, linked with hreflang
2

I also designed the brand and the logo. The riskiest parts are covered by node:test checks for JSON-LD escaping, safe redirects, document uploads, seller contacts and cron auth.

Security model

The browser only ever has the public Supabase key, so the database decides what each user can touch.

  • Row Level Security on every table. The default privileges of the anon and authenticated roles are revoked first.
  • Column-level grants let a seller edit their name, city, description and contacts, but not the verified flag, status, featured flag or slug.
  • A guard_publish trigger rejects any change to “published” that doesn’t come from the server role. A policy only sees the new row. The trigger sees the old one too.
  • No delete policy. Listings are archived instead, because an indexed page that returns 404 is worse than one marked “sold”.
  • Pedigree and vet-passport scans sit in a private storage bucket with no read policies. The moderator opens them through short-lived signed links.
  • Counters and rate limits run through SECURITY DEFINER functions that only the server can call.