ConceptA concept by Mayank Goel. Not affiliated with KangaCook or Roulettech. Recipes and photos belong to their authors on kangacook.com.

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 linesLinesShare
Quantity + known unit + item (confidence high)88885%
Count with no unit, or explicit “to taste” (medium)14814%
Kept verbatim, not scaled (low)50%
Scalable quantities91188%
“適量 / 少許 / to taste” (never scaled, never summed)11611%
Equipment lines (“1 non stick pan”)91%
Unparsed (no quantity at all, e.g. “義式香料”)50%

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.comthis concept
Performance score (all runs)95 (93, 96, 95)98 (98, 99, 98)
Largest Contentful Paint2.2 s2.0 s
First Contentful Paint1.8 s1.7 s
Total Blocking Time128 ms7 ms
Cumulative Layout Shift0.0030
Page weight (transfer)2.19 MB371 KB
of which images1.18 MB195 KB
Requests8621
SEO / Accessibility / Best practices92 / 96 / 7569 / 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)

  1. 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.
  2. Duplicate title suffix “| KangaCook | KangaCook” on about, blog, search, creators, sections and tag pages. Here titles use one template.
  3. 57 thin /search/* URLs in the sitemap (e.g. /search/醬油). The sitemap here lists only recipe, listing and audit pages, each with hreflang alternates.
  4. cache-control: private, no-store on every HTML response and x-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.
  5. 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.
  6. 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> and x-default.
  7. Recipe schema holes: no inLanguage, mainEntityOfPage, ItemList on listings, aggregateRating, video, prepTime. Here inLanguage, mainEntityOfPage, isBasedOn, step URLs and an ItemList are added. prepTime is 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.
  8. 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).
  9. Public Django admin login at api.kangacook.com/admin/login/. Not touched here; worth an allowlist.

4. What this concept does not do