Skip to the content
Georgi DimitrovdaTuzzo

Biogard

My first project: a pest-control company's site, rebuilt self-hosted

Role
Solo, directing agent fleets
Status
Live
Source
Private repository
Stack
Next.js 16React 19TypeScriptTailwind CSS 4PrismaPostgreSQL 17Docker ComposeCaddygVisorUmamiResendTailscale
Home page of the Biogard pest-control site: a dark hero with a Bulgarian headline about a pest-control firm in Ruse and the region, a white line drawing of a house in cross-section inside a dashed red outline, ants walking toward it and a mosquito overhead, and a request-inspection button. Phone numbers and the e-mail address are masked.
The real site, with the phone numbers and e-mail address masked.

In numbers

100

mobile PageSpeed score in all four categories (home page)

1.5s

mobile LCP, down from 2.7 s

122

pages crawled with 0 problems

38

hand-drawn SVGs: 27 pests, 9 places, 2 scenes

55

routes in two languages, each mapped to one primary keyword so pages do not compete

13

species in the hidden bug game, with a golden fly and a Cockroach King boss

The problem

Biogard is the first thing I built. My oldest saved chat on it is about a React app that would not start, and a hidden fly minigame was already in the plan early on. After that the site moved to Next.js 14 on Netlify with a Neon database. By the time of the rebuild it showed its age. The database's free tier ran out of quota and blanked the live blog (post pages returned 500). The icons were auto-traced and the FAQ had an 'Ask the AI' button.

The approach

I wrote a one-sentence brief: rebuild it to today's standard, keep the information and every URL, no AI look, hand-drawn illustrations, both languages. The agent wrote the shell first (i18n, routing proxy, SEO helpers, design tokens, header and footer), then fanned out eight agents in parallel across pages, blog, legal, APIs, illustration and SEO. A site crawler written during the fan-out became the quality gate. Production DNS then moved to the new server.

Biogard company logo: a chrome and orange shield with black insect silhouettes and a red banner reading БИОГАРД.
The company logo.

How it works

  1. A URL contract

    Bulgarian pages are unprefixed and English lives under /en; a proxy rewrites both into app/[lang] and 308-redirects old /bg/ paths. The build guide forbids renaming a slug, because the slugs are the indexed URLs. At cutover the crawler reported 122 pages and 0 problems.

  2. Hand-drawn SVG

    27 pest icons, 9 places and 2 scenes, drawn on a 64 by 64 grid with currentColor strokes, replaced the auto-traced set. I cut a generated hero video and put a drawn cutaway of a house in its place, with pests turning back at a red barrier. The copy rules ban em dashes and stock AI phrasing.

  3. Isolated on a shared host

    Docker Compose runs Caddy, Next.js and Postgres 17, and only Caddy publishes ports. Containers are read-only with all capabilities dropped, no-new-privileges and CPU, memory and PID limits; images are pinned by digest, and gVisor is available for the internet-facing services. An egress firewall chain stops the containers from opening connections into the tailnet, cloud metadata or private ranges. Releases are timestamped directories behind a current symlink with a rollback script, each deploy runs smoke tests that check host isolation, and the database is dumped nightly.

  4. SEO and compliance as a phase

    The SEO plan maps 55 routes in two languages to one primary keyword each, so pages do not compete for the same query, with titles of at most 60 characters, descriptions of 140 to 160, llms.txt and four sitemaps. The site publishes no prices, since each treatment is quoted individually. Claims that biocides are safe, non-toxic or eco were removed to comply with Article 72 of the EU Biocidal Products Regulation.

  5. A bug game the site does not mention

    Off by default. Typing 'biogard' or the Konami code, or tapping the logo seven times, wakes 13 species including a golden fly and a Cockroach King boss; 'ddd' fumigates the page, and moths swarm between 21:00 and 05:00. Analytics count who finds it and who plays. It keeps the site personal without getting in a customer's way.

The Biogard home page with its hidden bug game running: a red fly swatter, insects flying over the line-drawn house, a yellow badge reading plus 15, level 5, combo times 3 and new species Golden Fly, and a score bar at the lower left.
The hidden bug game, mid-run.

What I chose, and what lost

Chose

Self-host on an existing server, isolated by containers and an egress firewall

Over

A dedicated small server, which the runbook itself calls the safer design

I did not want a second server. The runbook records the accepted risk (code execution in the web container still needs a container escape to reach the host) and the exit: moving the site to its own box changes nothing in the app.

Chose

Google search ads

Over

Meta ads, after a test of 7 ads and 9,077 impressions

Search produced a phone call and a Viber message on its first day; the Meta test produced 0 calls. Meta was switched off.

Chose

No AI in the product: the FAQ's 'Ask the AI' button, its API route and the AI clauses in the legal pages were removed

Over

Keeping an AI answer box on the FAQ

I did not want visitors' questions sent to a model.

Outcome

Live at biogard.bg since the DNS cutover. After PR #39, Google PageSpeed Insights scored the mobile home page 100 in all four categories, with LCP down from 2.7 s to 1.5 s (the agent's own run, not repeated for this page). Self-hosted, cookieless Umami counts visitors, filtered to Bulgarian time or language after foreign bot traffic showed up. Unfinished: the Google reviews block is built and stays off until the listing has three ratings, double opt-in for the newsletter was dropped as friction and sits as dormant code, and there are only 24 unit test cases, so the crawler is the main gate. The ads method is now a written playbook in the ValoxVSL repo.

What comes next

Turn the reviews block on once the listing has three ratings.