You are the lead engineer on the joinpropio.com marketing site. It is a Next.js App Router project on the latest Next.js release, bundled with Turbopack, served behind Azure Front Door. Your job is to fix a specific, measured set of SEO, performance and security-header defects without changing the visual design.

Work in phases. Do not skip the measurement phases — the whole point is that we can prove the numbers moved.

## Ground truth

Measured 2026-07-21 from three sources: a Semrush site audit, a PageSpeed Insights run (Lighthouse 13.4.0, emulated Moto G Power, Slow 4G throttling), and direct header and HTML inspection. There is no CrUX field data for this domain, so everything below is lab data. Do not re-derive it — but do re-verify all of it at the end.

Lighthouse category scores, mobile: Performance 33, Accessibility 100, Best Practices 81, SEO 92, Agentic Browsing 2/3.

Metrics: FCP 2.3s, LCP 12.7s, TBT 23,020ms, CLS 0.002, Speed Index 20.9s.
CLS and FCP are fine. Do not spend effort on them. TBT is the dominant scoring problem.

Payload
- Total page weight 10,226 KiB.
- /scenes/propio_hero_scene.mp4 is 7,955 KiB of that — 78% of the page.
- Largest JS chunk is 304 KiB. 269 KiB of JavaScript is never used.
- assets.unicorn.studio/media/blue_noise_med.png is 207 KiB, cached for 1 hour.
- One font file, a85fe842…woff2, is 74.8 KiB.

Main thread
- 40.9s of main-thread work: 32,596ms "Other", 7,478ms script evaluation.
- 7.5s of JavaScript execution time.
- 20 long tasks. Worst offenders: chunks/35zw-rpbgmle4.js at 762ms and 613ms, posthog-recorder.js at 682ms.

Render blocking
- Three stylesheets block first paint: 77.7 KiB total, 1,970ms, est. savings 930ms.
- The largest is chunks/2v7g593o4q14t.css at 58.0 KiB / 980ms.
- 13 KiB of CSS in chunks/44012cj9gwxzh.css is unused.

Third parties
- cdn.jsdelivr.net serves unicornStudio.umd.js (46.6 KiB, 207ms main thread), three-bundle.js (163 KiB, 82.6 KiB unused), model-renderer.js (10 KiB).
- m.joinpropio.com proxies eight PostHog scripts, all with a 4-hour cache TTL: posthog-recorder (53 KiB), surveys (32 KiB, 25.8 KiB unused), conversations (22 KiB), dead-clicks-autocapture (7 KiB), exception-autocapture (5 KiB), web-vitals (3 KiB), logs (2 KiB), config (2 KiB).
- Azure Application Insights.

Images
- One /_next/image request is 800×800 rendered at 56×56, wasting 83.9 KiB.
- Another is 400×400 rendered at 56×56, wasting 17.8 KiB.
- Brand logos are JPEG/PNG rather than SVG. verizon and mcdonalds are each requested twice per page load.

Legacy JavaScript, 65.4 KiB wasted
- Polyfills and transpilation for Array.prototype.at, Array.prototype.flat, Array.prototype.flatMap, Object.fromEntries, Object.hasOwn, String.prototype.trimStart, String.prototype.trimEnd. The browserslist target is older than it needs to be.

Crawl and indexing
- GET /robots.txt returns 307 to /auth/login and serves the Auth0 Universal Login HTML page. Lighthouse parses 294 errors out of it. Line 14 of what crawlers receive is `<meta name="robots" content="noindex, nofollow">`. The same interception applies to /sitemap.xml.
- The served HTML contains zero `<h1>` elements. The hero heading is client-rendered only, and it is the LCP element.
- Both joinpropio.com and www.joinpropio.com return 200. The apex serves no canonical tag; www does. This produces the duplicate title, duplicate content and duplicate meta description errors in the Semrush audit.
- http://joinpropio.com/ returns 307, not 301.
- /llms.txt exists but has no H1 header and contains no links, so it fails the Agentic Browsing audit.

Delivery
- The homepage sends `cache-control: private, no-cache, no-store, max-age=0, must-revalidate` and `x-cache: CONFIG_NOCACHE`. Nothing is cached at the edge.
- TTFB is 2.3–2.9s unthrottled.
- The response body is 709,295 bytes of HTML with 53 script tags and roughly 28 JS chunks.
- Twelve woff2 files are preloaded via the `Link:` response header, across four families: Inter, Switzer, Cabinet Grotesk, Geist Mono.

Best Practices findings
- CSP `script-src` contains `'unsafe-inline'` — rated High.
- CSP relies on a host allowlist, which Lighthouse notes can frequently be bypassed — rated High. Nonces or hashes with `'strict-dynamic'` is the recommended fix.
- No Cross-Origin-Opener-Policy header — rated High.
- HSTS has a two-year max-age and includeSubDomains but no `preload` directive — rated Medium.
- No Trusted Types (`require-trusted-types-for`).
- Deprecated API in use: unload event listeners, in chunks/3p_9iscevnl9a.js.

## Phase 0 — Parallel diagnosis

Spawn five subagents concurrently. Each reads the codebase only — no edits in this phase. Each returns a structured report: file paths, line numbers, root cause, proposed fix, blast radius, and confidence.

1. crawl-agent — Find the middleware matcher and every route-protection rule. Determine exactly why /robots.txt, /sitemap.xml and /llms.txt reach the Auth0 login flow. Locate any existing app/robots.ts, app/sitemap.ts, or public/ equivalents. Report how metadata and canonical URLs are generated and where metadataBase is set.

2. render-agent — Trace the homepage component tree from app/layout to the hero. Identify every "use client" boundary and which are load-bearing. Determine specifically why the hero `<h1>` does not appear in server-rendered output: is the hero a client component, behind a mount guard, or dynamically imported with ssr: false? Report the minimum change that puts the real headline text into the initial HTML.

3. asset-agent — Inventory the hero media (the 7.8MB mp4: where it is referenced, whether it is `preload="auto"`, whether a poster exists), the Unicorn Studio integration and its mount point, the next/font configuration versus which weights CSS actually references, the PostHog init options, and every next/image usage on the homepage. Identify the 800×800-rendered-at-56×56 image and why verizon and mcdonalds are fetched twice.

4. infra-agent — Find where caching headers are set: next.config, middleware, custom headers(), route segment config, or Azure Front Door rules committed to the repo. Determine what forces dynamic rendering on the homepage — look for cookies(), headers(), searchParams, unstable_noStore, or `dynamic = "force-dynamic"`. Locate the browserslist or build target configuration responsible for the 65KB of polyfills.

5. security-agent — Locate where the CSP and security headers are set. Assess what it would take to move script-src from `'unsafe-inline'` plus host allowlist to nonces or hashes with `'strict-dynamic'`, given this is a Next.js app with inline bootstrap scripts. Identify what would break. Find the unload event listener. Report whether COOP can be enabled without breaking the Auth0, Plaid, Rainforest or Stripe popup flows — this needs checking, not assuming.

Wait for all five. Then produce a single merged plan ordered by measured impact, and show it to me before touching any code.

## Phase 1 — Baseline

Before any edits, capture a baseline so the improvement is provable:
- Run a production build. Record the route table: which routes are static, which dynamic, and First Load JS per route.
- Record the byte size of the homepage HTML response and the count of script tags.
- Record total transferred bytes for a cold homepage load.
- Run a Lighthouse mobile audit and save all five metrics plus the four category scores.

Write the baseline to a file. You will diff against it at the end.

## Phase 2 — Parallel implementation

Run these five workstreams concurrently, each in its own worktree. They overlap on three files, so fix ownership up front rather than resolving conflicts later: A owns the middleware matcher and next.config redirects, D owns next.config headers and route segment config, E owns the CSP and the middleware body. B owns client/server boundaries; C leaves them alone and works on assets, fonts and bundler config. Merge in order A, B, C, D, E, rebuilding between each.

### Workstream A — crawlability

Highest value, lowest risk. Do not batch it with anything else.

- Fix the middleware matcher so /robots.txt, /sitemap.xml, /llms.txt and any other public metadata route are never intercepted. Prefer an explicit negative lookahead in the matcher config over a runtime early-return, so middleware never runs for them at all.
- Implement app/robots.ts and app/sitemap.ts using the Next.js metadata file conventions. The sitemap must list every public marketing route with lastModified.
- Fix /llms.txt: it needs an H1 and links to the pages a model should actually read — product, pricing, integrations, docs.
- Pick one canonical host and 301 the other to it, at the Azure Front Door layer if possible and in next.config redirects if not. Emit a self-referencing canonical on every page from generateMetadata, driven off metadataBase.
- Change the http → https homepage redirect from 307 to 301.
- Give /privacy and every other route a per-page title and description instead of inheriting the homepage's.

### Workstream B — server-rendered content

- Move the hero heading into a server component so a real `<h1>` is present in the initial HTML. If the hero needs interactivity, keep the heading in the server tree and push only the interactive shell into a client child. Exactly one `<h1>` per page, containing the actual headline copy, surviving with JavaScript disabled.
- Audit every other "use client" boundary the render-agent flagged and move the ones that do not need it back to the server, reducing both the RSC payload and the hydration cost.
- Gate: curl the built page and grep for `<h1>`. That check must pass before this workstream is done.

### Workstream C — payload

This is where the score is. The 7.8MB video is the single biggest win on the page.

- Hero video: it must not be on the critical path. Options in order of preference: (a) replace it with a poster image and load the video on interaction or after LCP; (b) serve a short, heavily compressed, muted, looping WebM/AV1 at mobile resolution with a separate larger source for desktop; (c) drop it on mobile entirely via a media condition. Target under 500KB on mobile. Never `preload="auto"`.
- Unicorn Studio: stop loading it above the fold. Load after the LCP element has painted, on interaction, or behind an IntersectionObserver. Self-host the library instead of pulling three-bundle.js from jsDelivr; if that is not possible, add preconnect for cdn.jsdelivr.net and assets.unicorn.studio. Skip the WebGL scene entirely under prefers-reduced-motion. Self-host the 207KB noise texture so you control its cache TTL.
- PostHog: load after page load, not during. Disable session recording, dead-click autocapture, surveys and conversations on the marketing page — that is the 682ms long task and most of the wasted bytes. Keep pageview and conversion tracking. Serve the proxied scripts with a cache TTL far longer than 4 hours.
- CSS: 930ms is recoverable. Split the 58KB blocking stylesheet so only above-the-fold CSS blocks paint, and remove the 13KB of unused rules.
- Images: convert flat brand logos to SVG where you have the vector, otherwise WebP or AVIF. Fix the `sizes` prop so nothing requests an 800×800 asset to render at 56×56. Remove the duplicate verizon and mcdonalds requests.
- Fonts: cut to the weights actually referenced in CSS. Twelve preloaded files across four families is the problem, not the font technology. Set `preload: false` on any family not used above the fold, keep `display: "swap"`, and target at most three preloaded files. Check whether the 74.8KB font can be subset.
- Legacy JavaScript: update the browserslist target so `Array.prototype.at`, `flat`, `flatMap`, `Object.fromEntries`, `Object.hasOwn` and `trimStart`/`trimEnd` are not polyfilled. That is 65KB for free.
- Reduce the inlined RSC payload: any large static data structure passed from a server component into a client component is serialized into the HTML twice. Move it behind the client boundary or import it directly where it is used.
- Replace the deprecated unload event listener with pagehide or visibilitychange.

### Workstream D — caching and rendering mode

- Make the marketing homepage statically rendered. Remove or isolate whatever forces dynamic rendering. If one small piece genuinely needs request-time data, wrap only that piece in Suspense so the shell stays static.
- Replace the no-store cache-control on static marketing routes with a real policy: long max-age for immutable assets, stale-while-revalidate for HTML. Keep no-store on authenticated app routes only — do not weaken caching on anything under /auth or the product app.
- Verify the Azure Front Door rule set actually caches HTML afterward. `x-cache` should stop reporting CONFIG_NOCACHE on marketing pages.

### Workstream E — security headers

Do this carefully and merge it last. It is the workstream most likely to break something in production.

- Move CSP script-src from `'unsafe-inline'` plus host allowlist to nonces or hashes with `'strict-dynamic'`. Next.js supports nonce-based CSP through middleware. Test every third-party integration afterward: Auth0, Plaid, Rainforest, PostHog, Unicorn Studio, Application Insights.
- Add `Cross-Origin-Opener-Policy`. Verify first that it does not break OAuth or Plaid popup flows — if it does, report that and stop rather than shipping it.
- Add `preload` to the HSTS header only after confirming every subdomain is HTTPS-ready. Submitting to the preload list is effectively irreversible, so flag it as a decision for a human.
- Evaluate `require-trusted-types-for 'script'`. Report-only mode first.
- Lighthouse also flags missing source maps. Generate them and upload them to the error tracker rather than serving them publicly — readable stack traces without publishing first-party source.

## Phase 3 — Adversarial verification

Do not trust the implementation agents' self-reports. Spawn a fresh verification subagent that has not seen the implementation work, and give it only the ground-truth section above. It must independently confirm, against a real production build:

- `/robots.txt` and `/sitemap.xml` return 200 with the correct content-type, not a redirect, and Lighthouse reports zero robots.txt errors.
- `/llms.txt` has an H1 and links, and the Agentic Browsing audit passes.
- The homepage HTML contains exactly one `<h1>` with the real headline text.
- The non-canonical host 301s to the canonical one, and every page carries a self-referencing canonical.
- `http://` returns 301, not 307.
- Total transferred bytes for a cold homepage load, against the 10,226 KiB baseline.
- Homepage HTML byte size, script tag count, and preloaded font count, against the Phase 1 baseline.
- The build output marks the homepage as static.
- `cache-control` on the marketing homepage is no longer no-store, and `cache-control` on authenticated routes is still no-store.
- A re-run Lighthouse mobile audit: all five metrics and all four category scores against the baseline. Accessibility must still be 100.
- Every security header is present and no weaker than before.

Then spawn a second verification subagent whose only instruction is to try to break the result. Test with JavaScript disabled, reduced motion enabled, Slow 4G, a cold cache, a crawler user-agent, and each third-party flow that touches the CSP: Auth0 login, Plaid link, Rainforest payment, PostHog capture. Report anything that renders wrong, blocks, 404s, or throws a CSP violation.

## Phase 4 — Report

Produce one table: metric, before, after, delta — covering the five Lighthouse metrics, the four category scores, total bytes, HTML bytes, and TTFB. Then list anything you chose not to fix and why. Then list every place where you made a judgment call a reasonable engineer might make differently. I want to review those specifically.

## Rules throughout

- Do not change visual design, copy, or brand without asking. The rendered page stays pixel-identical unless I approve otherwise.
- Do not weaken any security header to make something load faster. If a performance fix seems to require it, stop and ask.
- Accessibility is at 100. Any change that regresses it gets reverted, no discussion.
- Prefer removing code over adding configuration.
- Every claim about an improvement must be backed by a command I can re-run myself.
- If a fix requires access you do not have — Azure Front Door rules, DNS records, the HSTS preload list — do not fake it. Write the exact change needed and flag it as a handoff item.
