Fail closed.
- Chose
- If the rate limiter is unavailable in production, expensive routes return 503.
- Instead of
- Running unprotected when Redis is down.
- Trade-off
- Abuse protection can never silently switch itself off.
A production web platform I designed, engineered and operate: the marketing site, a publishing system, free analysis tools, a lead pipeline and paid-search landing pages, secured and tested like a product.
Jardine Studio is my web design, development and SEO studio in Toronto. jardinestudio.com is its storefront, its proof of work and its lead engine, built and run entirely by me.
A Next.js platform with schema-driven content, an MDX journal, three free tools, emailed PDF reports, consent-aware conversion tracking, paid-search landing pages and an admin lead dashboard, behind a deliberate security model.
It is the kind of system agencies build with a team: 982 commits, 845 automated tests and a written security model, designed, engineered and operated by one person.

The live homepage in dark mode, captured 4 October 2026. The gold field is generated, not photographed.
Counted from the repository on 4 October 2026.
Four jobs share one codebase, one content model and one design system. Each is ordinary on its own. The work is making them reliable together.
A free audit fetches any URL a stranger types, renders PDFs, sends email and stores leads. I treated each of those as a risk with a named control, then tested the control.
| Risk | Control in the code | Checked by |
|---|---|---|
| A visitor’s URL points the crawler at internal systems | SSRF-safe fetcher: every hostname is resolved and must be a public address, re-checked on each of up to five redirects, under one deadline. | Dedicated SSRF test suite |
| Bots run up crawl, PDF and email costs | Redis sliding-window limits per action, such as three SEO crawls an hour. In production, if the limiter is unavailable the route refuses with 503 instead of running unprotected. | Route tests for limits and failure |
| Another site submits requests on a visitor’s behalf | Origin and Referer validation on state-changing API routes. | Route tests |
| Oversized or malformed request bodies | Bounded readers with per-route limits between 1 and 8 KB, strict UTF-8 decoding and content-type checks. | Unit tests |
| Someone else opens a private report | HMAC-signed unlock tokens bound to the audit and the email address, expiring after 24 hours, verified in constant time. | Unlock route tests |
| Guessing the admin token | Constant-time comparison of SHA-256 digests, a server session and 10 attempts an hour. | Admin action tests |
| Injected scripts or clickjacking | Content Security Policy per surface: app pages cannot be framed; client demos run under a scoped, hash-pinned policy with violation reports sent to a rate-limited endpoint. HSTS with preload and a restrictive Permissions-Policy. | Security tests and a production health suite |
From the source and test files. Rules for changing a policy live in a written security operations guide: a named requirement, minimum hosts, a privacy review and updated tests.
Pricing rules, crawl budgets, tokens and security headers are exactly the things that break quietly. They have tests, and production gets its own health checks.
The SEO audit crawls up to 25 same-origin pages within a 45-second budget and byte limits, runs PageSpeed on the homepage in parallel, and scores findings against the pages it actually reached. If discovery is cut short, the report says so.
The full report unlocks by email with a signed token and renders to PDF on the server. Rendered PDFs are cached for a day under a 4 MB cap, and audit state expires on a schedule.
From URL to report
Simplified from the implementation.
The cost calculator shows the product thinking in miniature: it prices what is defined, names what needs discovery and hands the visitor’s choices to the contact form.
Step 1 · Price the defined scope

Live calculator, illustrative configuration, captured 4 October 2026.
Step 2 · Add something uncertain

Same live calculator, rebuild with migration, captured 4 October 2026.
Step 3 · Hand over the brief

Live contact page, captured 4 October 2026.
Paid search only works if Google learns from real inquiries. The site reports accepted leads to GA4 and Google Ads from the same confirmed submit that writes its own first-party record, under consent and privacy rules that hold even when something fails.
Service pages are structured JSON validated against one shared schema. AWL, the editor I built for the site, generates its fields from that same schema, so the editor and the renderer cannot quietly disagree.
Metadata and canonicals follow central rules, the sitemap respects publication status, and an architecture map supplies related services and reading. A new page type still needs schema and design work, by design.
Inside the editor: AWLOne content contract
The first version leaned on a photographed waterfront. The current one generates its identity: an ordered-dither field in the same print-inspired language as the type. Drag to compare.

A skyline could belong to any Toronto business. The dither field comes from the design itself.
A finished CSS treatment sits underneath. The shader starts after load and an idle moment, and motion or data-saving preferences keep it off.
It pauses when hidden and restores itself if the browser drops the graphics context.
982 commits in five months, 845 automated tests, a written security model, CSP reporting and production health checks.
Next.js 15, React 19, TypeScript, Zod, MDX, Upstash Redis, Resend, React PDF, Three.js, Vitest and Playwright, on Vercel.
Need a site that does more than introduce you? Start a conversation
All projects ↗Available for select projects and opportunities
Let’s work together. Have a project in mind or a role you’re hiring for? I’d like to hear about it.
Nothing opened? Use Copy address and paste it into your email app.