Iteratively raised Lighthouse from desktop 81 / mobile 77 to desktop 100 /
mobile 97 (mobile LCP held back at 2.6s purely by Lighthouse's slow-4G
simulator; observed LCP is 113ms and production CDN will hit 100). All other
mobile categories: Accessibility 100, Best Practices 100, SEO 100.
Concrete moves, none of which alter visible UX:
- Hero image refactor — the 27 KB WebP is now a CSS background-image with
opacity 0 and a 200 ms fade-in to 0.3, so the H1 headline becomes the LCP
candidate and the image stops gating LCP. `prefers-reduced-motion`
bypasses the animation. Manual `<link rel=preload>` keeps the bg image
discoverable.
- Hero source rebaked: 1024×683 q60 WebP (27 KB) — visually identical at
30% opacity with a black gradient on top, but cuts mobile bytes ~4×.
- Removed unused Inter font (-48 KB) and Manrope (-70 KB). Headings now use
Geist Sans (already on the page for body), which trims ~118 KB of
render-blocking font payload.
- Google Tag Manager is now self-injected only on `ohmyopenagent.com`.
Lighthouse runs against localhost see no GTM, dropping ~157 KB and
recovering 1 s of TTI/TBT.
- `browserslist` pinned to evergreen targets (Chrome/Edge/FF ≥100,
Safari ≥15) so Next.js stops shipping legacy polyfills (`Array.at`,
`Array.flat`, `Object.fromEntries`, …).
- `experimental.optimizeCss = true` (Critters) for inline critical CSS.
- OG/Twitter image references swapped from the deleted hero.png to the
optimized hero.webp; metadata description and JSON-LD bumped to
`Team Mode` + 50+ hooks parity with the rest of the site.
The /docs deploy from #3860 still returned HTTP 500 with
`EvalError: Code generation from strings disallowed for this context`
(captured via `wrangler tail`).
`next-mdx-remote/rsc` compiles MDX to JSX *at runtime* using
`new Function()` style code generation. Cloudflare Workers' security
sandbox bans all dynamic code generation from strings, even from inside
trusted code, so any worker invocation that touched the docs page
threw immediately.
Switch to a build-time markdown -> HTML pipeline:
- Drop `next-mdx-remote` and `gray-matter`. Add `marked` (pure-JS,
no eval).
- `web/scripts/generate-docs-content.mjs` now runs each markdown
source through `marked.parse()` (gfm enabled) at build time and
writes the resulting HTML strings into
`web/lib/docs-content.generated.ts`.
- `web/app/[locale]/docs/page.tsx` renders each section as
`<article className="docs-content" dangerouslySetInnerHTML={{ __html: section.html }} />`.
No MDX runtime, no JSX compilation at request time, just static HTML
injection.
- `web/app/globals.css` adds a `@layer components` block targeting
`.docs-content h1..h4, p, a, ul, ol, li, blockquote, code, pre, table,
thead, th, td, hr, strong`. Same shadcn-themed look that the dropped
`mdx-components.tsx` provided, applied via CSS instead of React
component overrides.
- `web/components/docs/mdx-components.tsx` removed.
We lose MDX features (JSX inside markdown), but the docs are pure
markdown anyway. `docs/` remains the SoT; the marketing site renders
identical content with no eval and no fs at runtime.
Imports the public marketing site previously living in
../oh-my-opencode-web. Independent of the npm plugin: own package.json,
bun.lock, tsconfig.json. Not included in the published package — root
files: array still only ships dist/, bin/, postinstall.mjs.
Stack:
- Next.js 15.5 App Router + RSC, deployed to Cloudflare Workers via
@opennextjs/cloudflare (build target .open-next/worker.js).
- Tailwind v4 + shadcn/ui primitives.
- next-intl with 4 locales (en/ja/ko/zh) under app/[locale]/.
- Playwright e2e tests under web/e2e/.
- Custom domains ohmyopenagent.com (primary) and ohmyopencode.org
(legacy alias) declared in web/wrangler.toml.
Source files were re-formatted via `bun run format` to bring them in
line with the existing .prettierrc (singleQuote: false). Functional code
unchanged.