HerbLeaf

HerbLeaf — Store, content and community for indoor growing

A store for indoor-growing supplies and equipment with everything around it: guides, news, a glossary and a moderated community. Visitors learn, put a kit together and buy without leaving the site.

YEAR
2026 — present
TYPE
E-commerce + content · Production
ROLE
Full-Stack Developer
HERBLEAF: o site no computador e no celular
63
design screens reproduced on the site
50
versioned database migrations
3
layers of protection against prohibited terms on the storefront

Problem.

People starting an indoor grow buy blind: the catalog is large, content usually lives far from the store, and the community somewhere else again. HerbLeaf also has a factor that changes the engineering: payment and advertising platforms read the storefront, so what can appear on it has to be controlled end to end.

Solution.

A three-layer platform on a single site. The store has a catalog, cart, checkout with Mercado Pago, shipping through Melhor Envio and orders. The editorial layer brings news, guides, e-books, videos, a glossary, an events calendar and light and VPD calculators. The community has a feed, profiles, journals and messages, with moderation. An admin acts as the CMS for all of it.

Result.

Live at herbleaf.com.br, with all 63 screens of the design, the custom API, the admin, checkout, the compliance rules and the deploy pipeline. The community, already built with moderation, opens behind a release switch.

The hard part.

Every project has a point where the obvious solution doesn't cut it. Here, it was these.

Terms the storefront can't display, in three layers

Payment and advertising platforms' robots read the storefront: home, store, product, cart, checkout and institutional pages. A list of terms can't appear in any field there, whether text, meta, alt, structured data, slug or seed. The rule lives in three layers: a CI lint scans the public code; the backend rejects, with a 422 error pointing at the field path, any admin write to the storefront tables; and a migration fails if the production database still holds the term. The backend rejects instead of rewriting because silently rewriting would hide from the editor that their text changed. Editorial content follows a different rule, about content rather than vocabulary, so it stays out of that sweep.

Reproducing 63 screens without improving the design

The design export is the source of truth and doesn't get altered. It has no phone screens, so mobile is derived from desktop by written rules (grids going from 4 to 2 to 1 column, a sidebar that becomes chips, a hero that stacks) and approved in preview. The only change allowed without asking is one that doesn't alter the visual result, such as swapping a clickable span for a button with an accessibility label. Colors and sizes come from tokens, never loose hex in a component, and a fidelity review of all 63 screens fixed the high and medium divergences. The rest is recorded in design notes.

In payments, the server decides the price

The front end never talks to the database. The order reaches the API, which recalculates prices, shipping and coupon and only then creates the preference on Mercado Pago. The webhook checks the signature, queries the Mercado Pago API to confirm the payment, and is idempotent: stock is deducted exactly once, even if the notice arrives twice. Shipping uses Melhor Envio through OAuth, with automatic token renewal.

A community with moderation from day one

Text with sale or trade intent doesn't publish, whether in the feed, a comment or a private message. The same expression runs in the browser, which warns instantly, and on the server, which is the one that counts; changing one without the other would make the screen promise what the server refuses. Each person's first post goes into a queue, and the admin has approval, removal, bans and reports.

Automated content that arrives as a draft

News and events arrive through an automation that adapts the text to the house voice. Each item is identified by its source URL so it isn't duplicated, goes through the same compliance gate, and always lands as a draft in an admin queue to publish, edit or delete. The source's cover image isn't copied because of licensing: the article uses an editorial-section cover, and using the source image is left to the editor.

Docker alone doesn't restart what's stuck

With standalone Docker, a container marked unhealthy stays as it is, and only Swarm restarts it. A watchdog scheduled in systemd, every 5 minutes, checks the containers, restarts the ones that hung, watches memory and disk, and tests the public endpoint from outside, which catches tunnel problems even when the container is healthy. Every container has a memory ceiling, so a leak kills the container instead of taking down the machine. In the pipeline, a deploy that fails to boot rolls itself back to the previous image.

What I did.

  • An execution plan in blocks, with each product and design decision recorded and numbered
  • Front end in Next.js 16, React 19 and strict TypeScript, with Tailwind v4 and design tokens
  • A custom API in Node and TypeScript, the single point of data access, on Supabase (Postgres, storage and migrations)
  • Checkout with Mercado Pago Checkout Pro, shipping quotes through Melhor Envio, order tracking in the admin, coupons and stock deduction
  • Admin with a block editor, order management, catalog, content and a moderation queue
  • A GitHub Actions pipeline through to production, plus watchdog and backup routines on the VPS
  • Automated ingestion of news and events, always arriving as drafts
  • AI agents for marketing, in a sibling repository: one group to research suppliers and partnerships and another for SEO intelligence

Stack.

Next.js 16React 19TypeScriptTailwind v4Node.jsSupabase

Have a similar project?

Tell me the problem — I answer personally.