KangaCook site audit and what this concept changes
Every number on this page was measured by the scripts in this repo on the date shown. Nothing is quoted from memory. The concept is deliberately noindex: the recipes belong to their authors on kangacook.com, and the point of the hreflang and JSON-LD here is to show the architecture, not to compete for rankings.
1. The ingredient parser, measured
Data fetched 2026-10-18 from the public /api/recipes listing and each recipe page's JSON-LD: 151 recipes, 1,041 ingredient lines, 958 steps (910 with a photo).
| Result on the 1,041 lines | Lines | Share |
|---|---|---|
| Quantity + known unit + item (confidence high) | 888 | 85% |
| Count with no unit, or explicit “to taste” (medium) | 148 | 14% |
| Kept verbatim, not scaled (low) | 5 | 0% |
| Scalable quantities | 911 | 88% |
| “適量 / 少許 / to taste” (never scaled, never summed) | 116 | 11% |
| Equipment lines (“1 non stick pan”) | 9 | 1% |
| Unparsed (no quantity at all, e.g. “義式香料”) | 5 | 0% |
Timers were found in 103 of 958 steps from phrases like “煮 10 分鐘”. 1,524 distinct Chinese strings were machine-translated once and cached; every English page says so.
What the parser does with the hard cases: the create form forces a number, so authors type 1 鹽少許 or 1 味醂(1/2 大匙); the placeholder is dropped and the real quantity read. 一個瓶蓋 米酒 becomes 1 bottle cap ≈ 5 ml, labelled approximate. Simplified-Chinese lines (适量 盐, 两块 姜) and English lines (4 cloves garlic, 1 1/2 carrot) parse with the same grammar. The rules exist twice, in TypeScript (src/lib/ingredients.ts, used at build time by this app) and in Python (services/ingredient-api, a FastAPI service with the same tests, 1,041 of 1,041 lines matching), because KangaCook runs Django. The Python service is tested but not hosted; the app uses the TypeScript port.
2. Page speed, before and after, one recipe
Lighthouse 12.8.2, mobile preset, 3 runs each, median performance run shown. Measured 2026-10-18 from this machine (not CrUX field data). Before: https://kangacook.com/recipes/japanese-tamagoyaki-mabvos6d. After: https://kangacook.mayank-goel.com/en/recipes/japanese-tamagoyaki-mabvos6d.
| kangacook.com | this concept | |
|---|---|---|
| Performance score (all runs) | 95 (93, 96, 95) | 98 (98, 99, 98) |
| Largest Contentful Paint | 2.2 s | 2.0 s |
| First Contentful Paint | 1.8 s | 1.7 s |
| Total Blocking Time | 128 ms | 7 ms |
| Cumulative Layout Shift | 0.003 | 0 |
| Page weight (transfer) | 2.19 MB | 371 KB |
| of which images | 1.18 MB | 195 KB |
| Requests | 86 | 21 |
| SEO / Accessibility / Best practices | 92 / 96 / 75 | 69 / 100 / 100 |
Read the SEO column with care: this concept is noindex on purpose, and Lighthouse's only failing SEO audit here is “page is blocked from indexing”; kangacook.com's only failing one is a missing meta description on that recipe. kangacook.com's performance score is already good on this page; the measurable differences are page weight, image bytes, request count and Total Blocking Time, which are what the image pipeline and static rendering change.
The hero image pipeline on japanese-tamagoyaki-mabvos6d: the original cover PNG is 969 KB; this app serves it as 33 KB AVIF / 44 KB WebP at 640 px for phones and — / — at 1200 px, with srcset, fetchpriority="high" on the LCP image, served as static files from the CDN with a one-year immutable cache header. Nothing is hotlinked from the S3 bucket and nothing is republished at the original size.
3. Concrete fixes found on kangacook.com (verified 2026-10-17)
- Broken
SearchAction. The WebSite JSON-LD targets/recipes?q=…, which 307-redirects to/and drops the query, so the sitelinks search box can never work. Here the target is/en?q=…and the listing honours it. - Duplicate title suffix “| KangaCook | KangaCook” on about, blog, search, creators, sections and tag pages. Here titles use one template.
- 57 thin
/search/*URLs in the sitemap (e.g./search/醬油). The sitemap here lists only recipe, listing and audit pages, each with hreflang alternates. cache-control: private, no-storeon every HTML response andx-cache: Miss from cloudfront: every page view is a full Django round-trip. Here every page is prerendered at build time and served from the CDN.- Raw PNG as the LCP image (≈1 MB, 1024×768, no srcset, no cache header, served straight from S3 in us-east-1). See section 2.
- No hreflang and no server-rendered English. The 中/EN toggle is client-side only, so Google never sees English. Here
/en/…and/zh/…are real routes with<link rel="alternate" hreflang>andx-default. - Recipe schema holes: no
inLanguage,mainEntityOfPage,ItemListon listings,aggregateRating,video,prepTime. HereinLanguage,mainEntityOfPage,isBasedOn, step URLs and anItemListare added.prepTimeis still absent on purpose: the source data has no prep-time field, and inventing one would be a false claim; the fix is a field on the create form. - Security headers: none of HSTS, CSP, X-Content-Type-Options, Referrer-Policy, Permissions-Policy on kangacook.com. Here HSTS, nosniff, Referrer-Policy and Permissions-Policy are set (CSP is left for the real deployment because of AdSense and GA).
- Public Django admin login at
api.kangacook.com/admin/login/. Not touched here; worth an allowlist.
4. What this concept does not do
- It does not use the KangaCook logo, pretend to be KangaCook, or republish photos at their original size. Every recipe links back to its source and credits the author handle.
- English recipe text is machine-translated and unreviewed. A real relaunch needs an editor.
- The Python service is not deployed; it is a tested package in the repo. Hosting it on AWS behind the Next app is the obvious next step and matches KangaCook's stack.
- Nothing calls
api.kangacook.comor/admin. Data comes from the public listing and public recipe pages, fetched once at build time.