Skip to content

Fix Ahrefs SEO audit issues - #7561

Merged
lawrencecchen merged 6 commits into
mainfrom
feat-seo-ahrefs-actionables
Jul 11, 2026
Merged

lawrencecchen merged 6 commits into
mainfrom
feat-seo-ahrefs-actionables

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add shared SEO metadata helpers for complete Open Graph, Twitter, canonical, and longer descriptions
  • replace invalid global SoftwareApplication JSON-LD with Organization and WebSite schema
  • avoid locale-prefixed remote-tmux 404s, unlocalized legal redirects, and add edge cache headers for public marketing/docs pages
  • expose remote-tmux and AI-readable facts in /llms.txt and sitemap

Verification

  • bun test ./tests/seo.test.ts ./tests/security-headers.test.ts ./tests/agent-page-variants.test.ts
  • bun run typecheck
  • changed-file ESLint: zero errors, existing img warnings only
  • source scripts/load-dev-env.sh && bun run build
  • local production curl checks for /agents/codex OG tags, /ja/docs/agent-integrations/oh-my-opencode OG tags, /de/docs/remote-tmux redirect, /docs/getting-started cache header, and /llms.txt additions

Ahrefs mapping

  • Slow page: public docs/blog/agents/compare pages now get CDN cache headers
  • Open Graph incomplete: all generated page metadata now has OG title/description/url/site/image/type and Twitter summary image
  • Meta description too short: short descriptions are normalized through localized suffixes
  • Links to redirect and 404: legal footer links and remote-tmux locale fallbacks avoid bad internal targets
  • Rich-results validation error: removed invalid SoftwareApplication schema instead of adding fake reviews
  • Organic/AI visibility: Codex/OpenCode pages now have page-specific social metadata, and /llms.txt includes remote-tmux plus AI-readable facts

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Low Risk
Broad but mechanical metadata and routing changes with dedicated tests; no auth, payments, or core app logic touched.

Overview
Addresses Ahrefs SEO findings by centralizing metadata in @/i18n/seo and rolling it out across marketing, blog, docs, agents, compare, and related pages.

Metadata: seoDescription pads short copy with localized suffixes; openGraphDefaults and twitterSummary supply locale-aware OG/Twitter images, URLs, and types. Many blog posts drop hard-coded keywords in favor of the shared OG/Twitter pattern. Root layout drops the global keywords list and invalid SoftwareApplication JSON-LD in favor of Organization and WebSite schema.

OG image: opengraph-image is locale-aware (taglines, optional Noto fonts, English fallback) and uses safer font fetching.

Routing & links: Middleware redirects unsupported /<locale>/docs/remote-tmux to English and rewrites bare /docs/remote-tmux; guides/sitemap/llms.txt add Remote tmux where locales allow; footer legal links use plain anchors; homepage remote-tmux FAQ uses NextLink when docs aren’t localized.

Performance: Edge Cache-Control on explicit-locale public marketing paths (not unprefixed English-only routes).

Tests: New seo.test.ts plus updates to security-headers and agent-page-variants tests.

Reviewed by Cursor Bugbot for commit e440330. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes Ahrefs SEO issues by centralizing metadata and routing. Adds locale-aware OG/Twitter defaults, correct canonicals/alternates, normalized descriptions, and edge caching for localized pages, with more reliable OG image rendering.

  • New Features

    • Shared helpers in @/i18n/seo (seoDescription, openGraphDefaults, twitterSummary, openGraphImageTagline, canonicals/alternates) rolled out across landing, blog, compare, docs, guides, enterprise, and pricing; blog/docs layouts now use OG defaults.
    • Edge cache headers for explicit-locale public pages; /sitemap.xml and /llms.txt include Remote tmux per locale; opengraph-image loads locale fonts with a timeout and falls back to a default tagline if fonts aren’t available.
  • Bug Fixes

    • Normalized descriptions via seoDescription; ensured complete OG/Twitter fields, correct OG types (article/website), canonicals, and hreflang alternates.
    • Routing: 301 for unsupported /<locale>/docs/remote-tmux to /en/docs/remote-tmux; bare /docs/remote-tmux rewrites to English. Footer legal links stay unlocalized; homepage FAQ links use NextLink when docs aren’t localized. Tests added for SEO helpers, cache rules, and agent page variants.

Written for commit e440330. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Improvements

    • Standardized localized SEO metadata across marketing, documentation, agents, blogs, dashboard, and other landing pages.
    • Enhanced Open Graph and Twitter previews with canonical URLs, consistent localized descriptions, and shared helper outputs.
    • Updated structured data to emit Organization and WebSite JSON-LD.
    • Improved Open Graph image localization (localized tagline/typography and alt text).
  • New Features

    • Added Remote tmux documentation variants, sitemap coverage, and locale-aware routing.
  • Bug Fixes

    • Fixed unsupported Remote tmux locale routing via middleware redirects/rewrites.
    • Improved footer legal-link rendering for unlocalized links.
  • Tests

    • Expanded SEO, routing, and security-header coverage for these changes.

@vercel

vercel Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 11, 2026 3:02am
cmux-staging Building Building Preview, Comment Jul 11, 2026 3:02am

@coderabbitai

coderabbitai Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Localized SEO metadata

Layer / File(s) Summary
Shared SEO helpers and metadata
web/i18n/seo.ts, web/app/[locale]/...
Pages now use localized descriptions, canonical alternates, Open Graph defaults, and Twitter summaries. Layout JSON-LD and localized Open Graph image generation were also updated.
SEO validation
web/tests/seo.test.ts
Tests cover canonical URLs, localized descriptions, social metadata, fallback copy, image metadata, and middleware behavior.

Remote tmux localization

Layer / File(s) Summary
Route handling and discovery
web/proxy.ts, web/app/lib/agent-page-paths.ts, web/app/sitemap.ts, web/app/[locale]/(landing)/page.tsx
Remote tmux locale routing, agent-readable output, sitemap entries, and FAQ links now use supported locale data.
Coverage
web/tests/agent-page-variants.test.ts
Tests verify supported and unsupported locale variants in generated text and sitemap paths.

Presentation and cache rules

Layer / File(s) Summary
Footer links and marketing caching
web/app/[locale]/components/site-footer.tsx, web/security-headers.ts
Footer link types distinguish unlocalized links, and localized public marketing paths receive cache-control rules validated by tests.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 23 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description covers summary and verification, but it does not follow the required template sections for Testing, Demo Video, Review Trigger, or Checklist. Add the missing template sections: rename Verification to Testing, include a Demo Video link or note, paste the Review Trigger block, and complete the Checklist.
✅ Passed checks (23 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PR diff contains no Swift files; changes are all web/TS metadata, middleware, and tests.
Cmux Swift Blocking Runtime ✅ Passed No .swift files are changed in this PR diff, so the Swift blocking-runtime rule is not implicated.
Cmux Browser Automation Off-Main ✅ Passed Diff is web-only SEO/routing/cache work; no browser socket automation, routing, or policy-test files changed, so the off-main WebKit rule isn’t implicated.
Cmux Expensive Synchronous Load ✅ Passed No Swift files or agent-history load paths changed; diff is web/TS SEO/middleware work, so the Swift expensive-sync-load check is not applicable.
Cmux Cache Substitution Correctness ✅ Passed PR only changes SEO metadata, redirects, and cache headers; no fresh authoritative read was replaced by a cached value in persistence/history/undo/snapshot code.
Cmux No Hacky Sleeps ✅ Passed Scanned the changed runtime TS files and found no sleep/setTimeout/setInterval/polling/delay code; the new SEO/image helpers use fetch and URL composition only.
Cmux Algorithmic Complexity ✅ Passed Changes only add bounded scans over static locale/page lists and metadata helpers; no nested user-data rescans or hot-path sorting/filtering introduced.
Cmux Swift Concurrency ✅ Passed The PR diff touches only TS/TSX, JSON, and middleware/test files; no .swift, Package.swift, or Xcode project files are changed.
Cmux Swift @Concurrent ✅ Passed No Swift files changed in the PR diff, so the Swift @concurrent rule is not applicable.
Cmux Swift File And Package Boundaries ✅ Passed The diff only touches web/TS files; no Swift sources or SwiftPM boundaries are affected.
Cmux Swiftpm Lockfiles ✅ Passed Diff only touches web pages, SEO helpers, tests, and middleware; no Package.swift, Package.resolved, .gitignore, workflow, or Xcode project files changed.
Cmux Swift Logging ✅ Passed HEAD changes only web/TS files; no Swift files were modified, so no Swift logging violations exist in this diff.
Cmux User-Facing Error Privacy ✅ Passed Changed runtime text is SEO/metadata, redirects, and cache headers; no new user-facing errors, alerts, or API bodies expose vendor or internal details.
Cmux Full Internationalization ✅ Passed PASS: SEO helpers cover all 20 routing locales, remote-tmux is intentionally limited to en/ja with matching messages, and the OG image uses locale-aware tagline text.
Cmux Swiftui State Layout ✅ Passed No Swift/SwiftUI files are in the diff, so the SwiftUI state/layout rule is not applicable.
Cmux Architecture Rethink ✅ Passed PR changes only web/TypeScript files; no .swift paths are touched, so the Swift architectural rethink rule is not applicable.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The PR diff only changes web/TSX files; no Swift files or cmux auxiliary-window code was added or modified, so the shortcut rule doesn’t apply.
Cmux Source Artifacts ✅ Passed Changed paths are source, tests, locale catalogs, and config; no logs, temp dirs, build output, screenshots, or other artifacts appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No Swift files under Sources/ changed in this PR; all diffs are web/TS files, so the production-source seam rule is not applicable.
Cmux No Ambient Global State ✅ Passed PASS: the PR diff touches only web TS/TSX/JSON files (73 files, 0 .swift), and the rule applies only to production Swift code.
Title check ✅ Passed The title is concise and accurately summarizes the PR’s main SEO-audit fix.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-seo-ahrefs-actionables

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread web/proxy.ts
@greptile-apps

greptile-apps Bot commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR improves SEO metadata and discovery for public web pages. The main changes are:

  • Shared helpers for descriptions, Open Graph, Twitter cards, canonicals, and localized image metadata.
  • Updated metadata across marketing, docs, blog, agents, compare, pricing, and dashboard routes.
  • Replaced the root SoftwareApplication JSON-LD with Organization and WebSite schema.
  • Added remote-tmux routing, sitemap, guide, and AI-readable page coverage.
  • Added cache headers for locale-prefixed public marketing and docs pages.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
web/i18n/seo.ts Adds shared SEO helper functions and localized metadata tables.
web/app/[locale]/(landing)/opengraph-image.tsx Localizes the generated Open Graph image tagline and adds optional locale font loading.
web/proxy.ts Adds remote-tmux redirects and the default English rewrite.
web/app/lib/agent-page-paths.ts Registers remote-tmux for AI-readable variants and expands llms.txt facts.
web/app/sitemap.ts Adds remote-tmux sitemap entries for supported locales.

Reviews (12): Last reviewed commit: "Fallback OG tagline when locale font is ..." | Re-trigger Greptile

Comment thread web/i18n/seo.ts
Comment on lines +6 to +27
const shortDescriptionSuffixes: Record<string, string> = {
en: "Built for AI coding agents on macOS.",
ja: "macOS の AI コーディングエージェント向けです。",
"zh-CN": "面向 macOS 上的 AI 编码代理。",
"zh-TW": "面向 macOS 上的 AI 程式碼代理。",
ko: "macOS의 AI 코딩 에이전트를 위해 설계되었습니다.",
de: "Für KI-Coding-Agenten auf macOS entwickelt.",
es: "Creado para agentes de codificación con IA en macOS.",
fr: "Conçu pour les agents de codage IA sur macOS.",
it: "Creato per agenti di codifica IA su macOS.",
da: "Bygget til AI-kodeagenter på macOS.",
pl: "Stworzone dla agentów kodowania AI na macOS.",
ru: "Создано для AI-агентов программирования на macOS.",
bs: "Napravljeno za AI agente za kodiranje na macOS-u.",
ar: "مصمم لوكلاء البرمجة بالذكاء الاصطناعي على macOS.",
no: "Laget for AI-kodeagenter på macOS.",
"pt-BR": "Criado para agentes de código com IA no macOS.",
th: "สร้างมาเพื่อเอเจนต์เขียนโค้ด AI บน macOS.",
tr: "macOS'taki AI kodlama ajanları için tasarlandı.",
km: "បង្កើតសម្រាប់ភ្នាក់ងារ AI សរសេរកូដលើ macOS។",
uk: "Створено для AI-агентів програмування на macOS.",
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Metadata Copy Bypasses Locales

seoDescription() now appends these strings to localized meta, Open Graph, and Twitter descriptions across many production pages, but the copy lives in a hardcoded TypeScript table instead of the locale message catalogs. Translation updates and locale audits can miss this user-facing metadata, so supported locales can ship stale or inconsistent SEO text.

Rule Used: Flag production user-facing text that is not fully... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread web/i18n/seo.ts Outdated
url: DEFAULT_OG_IMAGE,
width: 1200,
height: 630,
alt: "cmux - The terminal built for multitasking",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Image Alt Stays English

openGraphDefaults() is spread into localized pages across the site, so this hardcoded English og:image:alt is emitted for non-English routes too. Because the string is not read from locale messages, those pages cannot provide translated image alt metadata for the supported locales.

Rule Used: Flag production user-facing text that is not fully... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@lawrencecchen
lawrencecchen force-pushed the feat-seo-ahrefs-actionables branch from 7012b28 to 091f83e Compare July 10, 2026 21:46
Comment thread web/app/[locale]/layout.tsx Outdated
Comment thread web/app/[locale]/(landing)/docs/getting-started/page.tsx
Comment thread web/i18n/seo.ts Outdated
Comment thread web/app/[locale]/(landing)/opengraph-image.tsx Outdated
Comment thread web/i18n/seo.ts
Comment thread web/i18n/seo.ts
@lawrencecchen
lawrencecchen force-pushed the feat-seo-ahrefs-actionables branch from 8e2020e to 3b4b0cc Compare July 10, 2026 22:21
Comment thread web/i18n/seo.ts Outdated
@lawrencecchen
lawrencecchen force-pushed the feat-seo-ahrefs-actionables branch from 3b4b0cc to 94cd1fa Compare July 10, 2026 22:24
Comment thread web/security-headers.ts
@lawrencecchen
lawrencecchen force-pushed the feat-seo-ahrefs-actionables branch 2 times, most recently from 51f95a7 to d5bec9e Compare July 10, 2026 22:34
Comment thread web/security-headers.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/app/`[locale]/(landing)/agents/claude-code/page.tsx:
- Around line 9-27: Extract the duplicated metadata composition from
generateMetadata into a shared async buildPageMetadata helper in the SEO module,
accepting locale, namespace, path, optional type, and availableLocales. Have the
helper perform translations, alternates, title/description, Open Graph, and
Twitter metadata construction, then update generateMetadata and other migrated
pages to call it with their page-specific values.

In `@web/app/`[locale]/(landing)/blog/cmd-shift-u/page.tsx:
- Around line 16-19: Replace the hardcoded keywords in the blog page metadata
with localized values from the blog.cmdShiftU namespace using the existing
next-intl pattern, such as t.raw("metaKeywords"). Add the corresponding
metaKeywords array to both web/messages/en.json and web/messages/ja.json,
ensuring the page’s metadata loader provides the translator.

In `@web/app/`[locale]/(landing)/blog/show-hn-launch/page.tsx:
- Around line 19-23: Replace the hardcoded keywords in the blog page metadata
with localized values from the blog.showHnLaunch translation namespace, using
the page’s existing next-intl translation pattern (such as
t.raw("metaKeywords")). Add the corresponding metaKeywords array to both
web/messages/en.json and web/messages/ja.json.

In `@web/app/`[locale]/(landing)/blog/zen-of-cmux/page.tsx:
- Around line 3-28: Extract the repeated metadata assembly into a shared
buildPageMetadata helper in seo.ts, accepting locale, path, title, description,
optional type, and availableLocales. Have it construct alternates, localized
description, openGraph, and twitter fields, then update generateMetadata and the
other duplicated page implementations to call this helper while preserving
article-specific fields such as publishedTime.

In `@web/app/`[locale]/(landing)/community/page.tsx:
- Around line 17-30: Extract the repeated metadata assembly into a shared
buildPageMetadata helper in the seo module, accepting locale, path, title,
rawDescription, and optional type. Move alternates, localized description,
openGraph defaults, canonical URL, and twitterSummary orchestration into this
helper, then simplify each page’s generateMetadata—including the community
page—to call it directly and update imports accordingly.

In `@web/app/`[locale]/(landing)/opengraph-image.tsx:
- Line 9: Update the route-level Open Graph metadata associated with the
exported alt value in opengraph-image.tsx so it uses the current locale’s
translation source, or remove the static alt export and rely on
openGraphDefaults as the authoritative localized metadata source; do not leave
an English-only value applied to every locale route.

In `@web/app/lib/agent-page-paths.ts`:
- Line 160: Add the /docs/remote-tmux route to the landing page registry in
guides/page.tsx, and add matching landing.links entries to both en.json and
ja.json, using the existing agentReadablePages and sitemap.ts entries as the
reference.

In `@web/security-headers.ts`:
- Around line 18-19: The hardcoded locale matcher in localePrefix duplicates the
canonical locale list. Import or derive the locale values from the routing
configuration in i18n/routing.ts, then construct localePrefix from that shared
source and update related next.config.ts and security-headers.test.ts usage to
reference the same definition.
- Around line 21-43: Add unprefixed English equivalents for the marketing routes
in publicMarketingSources, including docs, blog, agents, guides, compare, and
each listed static marketing page, so default-locale bare paths receive
publicMarketingCacheHeaders alongside localized paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 624ace30-b61f-4646-bb9f-9cc34dc0579f

📥 Commits

Reviewing files that changed from the base of the PR and between 2b122c4 and 94cd1fa.

📒 Files selected for processing (80)
  • web/app/[locale]/(landing)/agents/aider/page.tsx
  • web/app/[locale]/(landing)/agents/amp/page.tsx
  • web/app/[locale]/(landing)/agents/claude-code/page.tsx
  • web/app/[locale]/(landing)/agents/codex/page.tsx
  • web/app/[locale]/(landing)/agents/cursor-cli/page.tsx
  • web/app/[locale]/(landing)/agents/gemini-cli/page.tsx
  • web/app/[locale]/(landing)/agents/opencode/page.tsx
  • web/app/[locale]/(landing)/agents/page.tsx
  • web/app/[locale]/(landing)/assets/page.tsx
  • web/app/[locale]/(landing)/best-terminal-for-mac/page.tsx
  • web/app/[locale]/(landing)/blog/claude-code-best-worktree-manager/page.tsx
  • web/app/[locale]/(landing)/blog/cmd-shift-u/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-claude-teams/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-finder/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-history/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-home/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-omo/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-ssh/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-vault/page.tsx
  • web/app/[locale]/(landing)/blog/gpl/page.tsx
  • web/app/[locale]/(landing)/blog/introducing-cmux/page.tsx
  • web/app/[locale]/(landing)/blog/layout.tsx
  • web/app/[locale]/(landing)/blog/markdown-viewer/page.tsx
  • web/app/[locale]/(landing)/blog/page.tsx
  • web/app/[locale]/(landing)/blog/passkey-auth/page.tsx
  • web/app/[locale]/(landing)/blog/session-restore/page.tsx
  • web/app/[locale]/(landing)/blog/show-hn-launch/page.tsx
  • web/app/[locale]/(landing)/blog/task-manager/page.tsx
  • web/app/[locale]/(landing)/blog/unread-shortcuts/page.tsx
  • web/app/[locale]/(landing)/blog/zen-of-cmux/page.tsx
  • web/app/[locale]/(landing)/built-on-ghostty/page.tsx
  • web/app/[locale]/(landing)/community/page.tsx
  • web/app/[locale]/(landing)/compare/[slug]/page.tsx
  • web/app/[locale]/(landing)/compare/page.tsx
  • web/app/[locale]/(landing)/deeplink/[kind]/page.tsx
  • web/app/[locale]/(landing)/docs/agent-integrations/claude-code-teams/page.tsx
  • web/app/[locale]/(landing)/docs/agent-integrations/oh-my-claudecode/page.tsx
  • web/app/[locale]/(landing)/docs/agent-integrations/oh-my-codex/page.tsx
  • web/app/[locale]/(landing)/docs/agent-integrations/oh-my-opencode/page.tsx
  • web/app/[locale]/(landing)/docs/api/page.tsx
  • web/app/[locale]/(landing)/docs/browser-automation/page.tsx
  • web/app/[locale]/(landing)/docs/changelog/page.tsx
  • web/app/[locale]/(landing)/docs/concepts/page.tsx
  • web/app/[locale]/(landing)/docs/configuration/page.tsx
  • web/app/[locale]/(landing)/docs/custom-commands/page.tsx
  • web/app/[locale]/(landing)/docs/dock/page.tsx
  • web/app/[locale]/(landing)/docs/getting-started/page.tsx
  • web/app/[locale]/(landing)/docs/ios/page.tsx
  • web/app/[locale]/(landing)/docs/keyboard-shortcuts/page.tsx
  • web/app/[locale]/(landing)/docs/layout.tsx
  • web/app/[locale]/(landing)/docs/notifications/page.tsx
  • web/app/[locale]/(landing)/docs/remote-tmux/page.tsx
  • web/app/[locale]/(landing)/docs/session-restore/page.tsx
  • web/app/[locale]/(landing)/docs/skills/page.tsx
  • web/app/[locale]/(landing)/docs/ssh/page.tsx
  • web/app/[locale]/(landing)/docs/task-manager/page.tsx
  • web/app/[locale]/(landing)/docs/textbox/page.tsx
  • web/app/[locale]/(landing)/docs/vault/page.tsx
  • web/app/[locale]/(landing)/docs/workspace-groups/page.tsx
  • web/app/[locale]/(landing)/download/confirmation/page.tsx
  • web/app/[locale]/(landing)/guides/page.tsx
  • web/app/[locale]/(landing)/ios/page.tsx
  • web/app/[locale]/(landing)/nightly/page.tsx
  • web/app/[locale]/(landing)/opengraph-image.tsx
  • web/app/[locale]/(landing)/page.tsx
  • web/app/[locale]/(landing)/wall-of-love/page.tsx
  • web/app/[locale]/components/site-footer.tsx
  • web/app/[locale]/dashboard/subrouter/page.tsx
  • web/app/[locale]/docs/base/page.tsx
  • web/app/[locale]/enterprise/page.tsx
  • web/app/[locale]/layout.tsx
  • web/app/[locale]/pricing/page.tsx
  • web/app/lib/agent-page-paths.ts
  • web/app/sitemap.ts
  • web/i18n/seo.ts
  • web/proxy.ts
  • web/security-headers.ts
  • web/tests/agent-page-variants.test.ts
  • web/tests/security-headers.test.ts
  • web/tests/seo.test.ts

Comment on lines 9 to 27
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "landing.claude" });
const alternates = buildAlternates(locale, "/agents/claude-code");
const title = t("metaTitle");
const description = seoDescription(locale, t("metaDescription"));
return {
title: t("metaTitle"),
description: t("metaDescription"),
alternates: buildAlternates(locale, "/agents/claude-code"),
title,
description,
alternates,
openGraph: {
...openGraphDefaults(locale, "website"),
title,
description,
url: alternates.canonical,
},
twitter: twitterSummary(locale, title, description),
};
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Extract the repeated generateMetadata boilerplate into a shared helper.

The alternates → title/description → { title, description, alternates, openGraph, twitter } composition here is duplicated verbatim (only namespace/path/type differ) across dozens of migrated pages in this PR. Consider a single buildPageMetadata(locale, { namespace, path, type, availableLocales }) in @/i18n/seo that each page calls with 3-4 arguments, reducing ~15 lines of copy-paste per page and eliminating the risk of one page silently drifting from the pattern during future edits.

♻️ Sketch of a consolidated helper (in `web/i18n/seo.ts`)
export async function buildPageMetadata(
  locale: string,
  namespace: string,
  path: string,
  type: "website" | "article" = "website",
  availableLocales?: readonly string[],
) {
  const t = await getTranslations({ locale, namespace });
  const alternates = buildAlternates(locale, path, availableLocales);
  const title = t("metaTitle");
  const description = seoDescription(locale, t("metaDescription"));
  return {
    title,
    description,
    alternates,
    openGraph: { ...openGraphDefaults(locale, type), title, description, url: alternates.canonical },
    twitter: twitterSummary(locale, title, description),
  };
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/`[locale]/(landing)/agents/claude-code/page.tsx around lines 9 - 27,
Extract the duplicated metadata composition from generateMetadata into a shared
async buildPageMetadata helper in the SEO module, accepting locale, namespace,
path, optional type, and availableLocales. Have the helper perform translations,
alternates, title/description, Open Graph, and Twitter metadata construction,
then update generateMetadata and other migrated pages to call it with their
page-specific values.

Comment on lines 16 to 19
keywords: [
"cmux", "terminal", "macOS", "notifications", "AI coding agents",
"keyboard shortcuts", "developer tools", "workflow",
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use localized keywords for consistency with other blog pages.

The keywords array is hardcoded in English, while other blog pages in this PR (markdown-viewer, passkey-auth, session-restore, task-manager, unread-shortcuts) use t.raw("metaKeywords") for localized keywords. Per coding guidelines, web metadata must use next-intl or another locale-specific source. The same pattern appears in show-hn-launch/page.tsx.

As per coding guidelines, web/**/*.{ts,tsx,md,mdx,json}: "User-facing web UI text, API copy, rendered markdown, changelog text, metadata, route copy, and message keys must use next-intl or another locale-specific source and be represented for every supported locale."

♻️ Suggested fix: use localized keywords
-    keywords: [
-      "cmux", "terminal", "macOS", "notifications", "AI coding agents",
-      "keyboard shortcuts", "developer tools", "workflow",
-    ],
+    const rawKeywords = t.raw("metaKeywords");
+    const keywords = Array.isArray(rawKeywords)
+      ? rawKeywords.filter((keyword): keyword is string => typeof keyword === "string")
+      : [];

Add the metaKeywords array to the blog.cmdShiftU namespace in web/messages/en.json and web/messages/ja.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/`[locale]/(landing)/blog/cmd-shift-u/page.tsx around lines 16 - 19,
Replace the hardcoded keywords in the blog page metadata with localized values
from the blog.cmdShiftU namespace using the existing next-intl pattern, such as
t.raw("metaKeywords"). Add the corresponding metaKeywords array to both
web/messages/en.json and web/messages/ja.json, ensuring the page’s metadata
loader provides the translator.

Source: Coding guidelines

Comment on lines 19 to 23
keywords: [
"cmux", "Show HN", "Hacker News", "terminal", "macOS", "Ghostty",
"libghostty", "AI coding agents", "Claude Code", "Codex", "launch",
"vertical tabs", "notification rings",
],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use localized keywords for consistency with other blog pages.

Same as cmd-shift-u/page.tsx — the keywords array is hardcoded in English while other blog pages in this PR use t.raw("metaKeywords") for localized keywords. Per coding guidelines, web metadata must use next-intl or another locale-specific source.

As per coding guidelines, web/**/*.{ts,tsx,md,mdx,json}: "metadata ... must use next-intl or another locale-specific source and be represented for every supported locale."

♻️ Suggested fix: use localized keywords
-    keywords: [
-      "cmux", "Show HN", "Hacker News", "terminal", "macOS", "Ghostty",
-      "libghostty", "AI coding agents", "Claude Code", "Codex", "launch",
-      "vertical tabs", "notification rings",
-    ],
+    const rawKeywords = t.raw("metaKeywords");
+    const keywords = Array.isArray(rawKeywords)
+      ? rawKeywords.filter((keyword): keyword is string => typeof keyword === "string")
+      : [];

Add the metaKeywords array to the blog.showHnLaunch namespace in web/messages/en.json and web/messages/ja.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/`[locale]/(landing)/blog/show-hn-launch/page.tsx around lines 19 -
23, Replace the hardcoded keywords in the blog page metadata with localized
values from the blog.showHnLaunch translation namespace, using the page’s
existing next-intl translation pattern (such as t.raw("metaKeywords")). Add the
corresponding metaKeywords array to both web/messages/en.json and
web/messages/ja.json.

Source: Coding guidelines

Comment on lines +3 to +28
import { buildAlternates, openGraphDefaults, seoDescription, twitterSummary } from "@/i18n/seo";
import { BlogSchema } from "../blog-schema";
import { Link } from "@/i18n/navigation";

export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: "blog.zenOfCmux" });
const alternates = buildAlternates(locale, "/blog/zen-of-cmux");
const title = t("metaTitle");
const description = seoDescription(locale, t("metaDescription"));
return {
title: t("metaTitle"),
description: t("metaDescription"),
title,
description,
keywords: [
"cmux", "terminal", "macOS", "CLI", "composable",
"developer tools", "AI coding agents", "workflow",
],
openGraph: {
title: t("metaTitle"),
description: t("metaDescription"),
type: "article",
...openGraphDefaults(locale, "article"),
title,
description,
url: alternates.canonical,
publishedTime: "2026-02-27T00:00:00Z",
},
twitter: {
card: "summary_large_image",
title: t("metaTitle"),
description: t("metaDescription"),
},
alternates: buildAlternates(locale, "/blog/zen-of-cmux"),
twitter: twitterSummary(locale, title, description),
alternates,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

LGTM on the metadata migration itself, but flag the recurring duplication pattern.

The alternates → title/description → openGraph/twitter construction here is copy-pasted nearly verbatim across dozens of page files in this PR (built-on-ghostty, guides, ios, nightly, wall-of-love, dashboard/subrouter, docs/base, and many more per the stack outline). Consider extracting a single buildPageMetadata(locale, path, { title, description, type, availableLocales }) helper into web/i18n/seo.ts that returns the full { title, description, alternates, openGraph, twitter } object, so each page only supplies its translated title/description and og type. This centralizes future changes (e.g. adding a new social field) to one place instead of 60+ call sites.

♻️ Sketch of a consolidating helper
// web/i18n/seo.ts
export function buildPageMetadata(
  locale: string,
  path: string,
  opts: { title: string; description: string; type?: "website" | "article"; availableLocales?: readonly string[] },
) {
  const alternates = buildAlternates(locale, path, opts.availableLocales);
  const description = seoDescription(locale, opts.description);
  return {
    title: opts.title,
    description,
    alternates,
    openGraph: {
      ...openGraphDefaults(locale, opts.type ?? "website"),
      title: opts.title,
      description,
      url: alternates.canonical,
    },
    twitter: twitterSummary(locale, opts.title, description),
  };
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/`[locale]/(landing)/blog/zen-of-cmux/page.tsx around lines 3 - 28,
Extract the repeated metadata assembly into a shared buildPageMetadata helper in
seo.ts, accepting locale, path, title, description, optional type, and
availableLocales. Have it construct alternates, localized description,
openGraph, and twitter fields, then update generateMetadata and the other
duplicated page implementations to call this helper while preserving
article-specific fields such as publishedTime.

Comment on lines +17 to +30
const alternates = buildAlternates(locale, "/community");
const title = t("metaTitle");
const description = seoDescription(locale, t("metaDescription"));
return {
title: t("metaTitle"),
description: t("metaDescription"),
alternates: buildAlternates(locale, "/community"),
title,
description,
alternates,
openGraph: {
...openGraphDefaults(locale, "website"),
title,
description,
url: alternates.canonical,
},
twitter: twitterSummary(locale, title, description),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting a shared buildPageMetadata helper to eliminate duplicated orchestration.

The same generateMetadata body pattern—compute alternates, derive title/description, spread openGraphDefaults, set url: alternates.canonical, and call twitterSummary—is repeated across all 60+ pages in this PR. A single higher-level helper in @/i18n/seo would centralize the orchestration and reduce each page's generateMetadata to a one-liner.

♻️ Proposed shared helper and simplified call site

Add to web/i18n/seo.ts:

export function buildPageMetadata(
  locale: string,
  path: string,
  title: string,
  rawDescription: string,
  type: "website" | "article" = "website",
) {
  const alternates = buildAlternates(locale, path);
  const description = seoDescription(locale, rawDescription);
  return {
    title,
    description,
    alternates,
    openGraph: {
      ...openGraphDefaults(locale, type),
      title,
      description,
      url: alternates.canonical,
    },
    twitter: twitterSummary(locale, title, description),
  };
}

Then each page becomes:

 export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
   const { locale } = await params;
   const t = await getTranslations({ locale, namespace: "community" });
-  const alternates = buildAlternates(locale, "/community");
-  const title = t("metaTitle");
-  const description = seoDescription(locale, t("metaDescription"));
-  return {
-    title,
-    description,
-    alternates,
-    openGraph: {
-      ...openGraphDefaults(locale, "website"),
-      title,
-      description,
-      url: alternates.canonical,
-    },
-    twitter: twitterSummary(locale, title, description),
-  };
+  return buildPageMetadata(locale, "/community", t("metaTitle"), t("metaDescription"), "website");
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const alternates = buildAlternates(locale, "/community");
const title = t("metaTitle");
const description = seoDescription(locale, t("metaDescription"));
return {
title: t("metaTitle"),
description: t("metaDescription"),
alternates: buildAlternates(locale, "/community"),
title,
description,
alternates,
openGraph: {
...openGraphDefaults(locale, "website"),
title,
description,
url: alternates.canonical,
},
twitter: twitterSummary(locale, title, description),
const alternates = buildAlternates(locale, "/community");
const title = t("metaTitle");
const description = seoDescription(locale, t("metaDescription"));
return {
title,
description,
alternates,
openGraph: {
...openGraphDefaults(locale, "website"),
title,
description,
url: alternates.canonical,
},
twitter: twitterSummary(locale, title, description),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/`[locale]/(landing)/community/page.tsx around lines 17 - 30, Extract
the repeated metadata assembly into a shared buildPageMetadata helper in the seo
module, accepting locale, path, title, rawDescription, and optional type. Move
alternates, localized description, openGraph defaults, canonical URL, and
twitterSummary orchestration into this helper, then simplify each page’s
generateMetadata—including the community page—to call it directly and update
imports accordingly.

Comment thread web/app/lib/agent-page-paths.ts
Comment thread web/security-headers.ts
Comment on lines +18 to +19
const localePrefix =
":locale(ja|zh-CN|zh-TW|ko|de|es|fr|it|da|pl|ru|bs|ar|no|pt-BR|th|tr|km|uk)";

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm routing.ts exports a locale list and where localePrefix is duplicated
fd -t f 'routing.ts' web/i18n
rg -nP '(locales|localePrefix)\b' web/i18n/routing.ts web/next.config.ts web/security-headers.ts

Repository: manaflow-ai/cmux

Length of output: 1217


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== routing.ts ==\n'
cat -n web/i18n/routing.ts | sed -n '1,120p'

printf '\n== next.config.ts ==\n'
cat -n web/next.config.ts | sed -n '1,120p'

printf '\n== security-headers.ts ==\n'
cat -n web/security-headers.ts | sed -n '1,120p'

printf '\n== security-headers test ==\n'
cat -n web/tests/security-headers.test.ts | sed -n '1,200p'

Repository: manaflow-ai/cmux

Length of output: 7998


Derive the locale matcher from web/i18n/routing.ts. localePrefix is hardcoded here and duplicated in web/next.config.ts and web/tests/security-headers.test.ts, while the canonical locale list lives in web/i18n/routing.ts. Pulling it from that source keeps redirects, cache rules, and the test in sync when locales change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/security-headers.ts` around lines 18 - 19, The hardcoded locale matcher
in localePrefix duplicates the canonical locale list. Import or derive the
locale values from the routing configuration in i18n/routing.ts, then construct
localePrefix from that shared source and update related next.config.ts and
security-headers.test.ts usage to reference the same definition.

Comment thread web/security-headers.ts
@lawrencecchen
lawrencecchen force-pushed the feat-seo-ahrefs-actionables branch from d5bec9e to d64ce91 Compare July 10, 2026 22:39

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

There are 5 total unresolved issues (including 3 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d64ce91. Configure here.

Comment thread web/app/[locale]/(landing)/opengraph-image.tsx
Comment thread web/app/[locale]/(landing)/opengraph-image.tsx Outdated
@lawrencecchen
lawrencecchen force-pushed the feat-seo-ahrefs-actionables branch from d64ce91 to 87b3f4e Compare July 10, 2026 22:42
@lawrencecchen
lawrencecchen force-pushed the feat-seo-ahrefs-actionables branch from 87b3f4e to 1c397f8 Compare July 10, 2026 22:47

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
web/tests/security-headers.test.ts (1)

22-42: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Avoid duplicating the locale alternation in the test web/tests/security-headers.test.ts hardcodes the same locale list as web/security-headers.ts, so adding or removing a locale in web/i18n/routing.ts still leaves two manual copies to update. Derive the alternation from the shared routing config (excluding the default locale) so the route config and test stay in sync.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/tests/security-headers.test.ts` around lines 22 - 42, The test hardcodes
the locale alternation instead of using the shared routing configuration. Update
the test near the “caches only explicit-locale public marketing pages at the
edge” case to derive the localized route pattern from the locales exported by
the shared i18n routing config, excluding the default locale, and use that
generated pattern when matching `securityHeaderRules`; avoid duplicating locale
values.
web/app/[locale]/(landing)/blog/cmux-history/page.tsx (1)

21-36: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use localized keywords for consistency with other blog pages.

The keywords array is hardcoded in English, while other blog pages in this PR use t.raw("metaKeywords") for localized keywords. Per coding guidelines, web metadata must use next-intl or another locale-specific source. As per coding guidelines, web/**/*.{ts,tsx,md,mdx,json}: "User-facing web UI text, API copy, rendered markdown, changelog text, metadata, route copy, and message keys must use next-intl or another locale-specific source and be represented for every supported locale."

♻️ Suggested fix: use localized keywords
-    keywords: [
-      "cmux",
-      "history",
-      "reopen closed terminal",
-      "restore closed tab",
-      "reopen terminal tab",
-      "reopen closed workspace",
-      "terminal history",
-      "Cmd+Shift+T",
-      "focus history",
-      "Claude Code",
-      "Codex",
-      "OpenCode",
-      "macOS",
-      "AI coding agents",
-    ],
+    const rawKeywords = t.raw("metaKeywords");
+    const keywords = Array.isArray(rawKeywords)
+      ? rawKeywords.filter((keyword): keyword is string => typeof keyword === "string")
+      : [];

Add the metaKeywords array to the blog.cmuxHistory namespace in web/messages/en.json and web/messages/ja.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/`[locale]/(landing)/blog/cmux-history/page.tsx around lines 21 - 36,
Replace the hardcoded English keywords in the blog page metadata with the
localized `t.raw("metaKeywords")` value, following the pattern used by other
blog pages. Add the corresponding `metaKeywords` array to the `blog.cmuxHistory`
namespace in both `en.json` and `ja.json`, ensuring every supported locale has
translations.

Source: Coding guidelines

web/app/[locale]/(landing)/blog/gpl/page.tsx (1)

16-19: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use localized keywords for consistency with other blog pages.

The keywords array is hardcoded in English while blog/cmux-vault/page.tsx in this same PR uses t.raw("metaKeywords") for localized keywords. Per coding guidelines, web metadata must use next-intl or another locale-specific source and be represented for every supported locale.

♻️ Suggested fix: use localized keywords
-    keywords: [
-      "cmux", "GPL", "AGPL", "open source", "license",
-      "terminal", "macOS", "copyleft",
-    ],
+    keywords,

Add before the return statement:

+  const rawKeywords = t.raw("metaKeywords");
+  const keywords = Array.isArray(rawKeywords)
+    ? rawKeywords.filter((keyword): keyword is string => typeof keyword === "string")
+    : [];

Add the metaKeywords array to the blog.gpl namespace in web/messages/en.json and web/messages/ja.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/`[locale]/(landing)/blog/gpl/page.tsx around lines 16 - 19, Replace
the hardcoded keywords in the GPL page metadata with a localized `metaKeywords`
value obtained via `next-intl` (for example, `t.raw("metaKeywords")`), following
`blog/cmux-vault/page.tsx`; add the corresponding `metaKeywords` array to the
`blog.gpl` namespace in both `web/messages/en.json` and `web/messages/ja.json`.

Sources: Coding guidelines, Path instructions

♻️ Duplicate comments (1)
web/app/[locale]/(landing)/blog/show-hn-launch/page.tsx (1)

19-23: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Hardcoded English keywords still not localized — previously flagged.

This issue was raised in a prior review comment but remains unaddressed. The keywords array at lines 19-23 is hardcoded in English while blog/cmux-vault/page.tsx uses t.raw("metaKeywords") for localized keywords. Per coding guidelines, web metadata must use next-intl or another locale-specific source.

♻️ Suggested fix: use localized keywords
-    keywords: [
-      "cmux", "Show HN", "Hacker News", "terminal", "macOS", "Ghostty",
-      "libghostty", "AI coding agents", "Claude Code", "Codex", "launch",
-      "vertical tabs", "notification rings",
-    ],
+    keywords,

Add before the return statement:

+  const rawKeywords = t.raw("metaKeywords");
+  const keywords = Array.isArray(rawKeywords)
+    ? rawKeywords.filter((keyword): keyword is string => typeof keyword === "string")
+    : [];

Add the metaKeywords array to the blog.showHnLaunch namespace in web/messages/en.json and web/messages/ja.json.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/`[locale]/(landing)/blog/show-hn-launch/page.tsx around lines 19 -
23, Replace the hardcoded keywords in the page’s metadata with localized values
retrieved via the existing next-intl translation mechanism, such as
t.raw("metaKeywords") before the return. Add the corresponding metaKeywords
array to the blog.showHnLaunch namespaces in both en.json and ja.json, matching
the pattern used by blog/cmux-vault.

Sources: Coding guidelines, Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/app/`[locale]/(landing)/opengraph-image.tsx:
- Around line 82-84: Make the locale font request in the Promise expression used
by the OG image generation non-fatal: check res.ok before converting the
response to an ArrayBuffer, return null for non-OK responses, and catch network
errors by returning null. Preserve the existing localeFont and localeFontData
guard so ImageResponse proceeds without the font when fetching fails.
- Line 9: Update the Open Graph metadata in the landing-page image module so it
no longer hard-codes English text: use the locale-aware `openGraphImage(locale)`
or `openGraphDefaults(locale, ...)` from `web/i18n/seo.ts`, or remove the
redundant `alt` export if the shared metadata already supplies it.

In `@web/i18n/seo.ts`:
- Around line 4-5: Remove the unused DEFAULT_OG_IMAGE constant from seo.ts,
leaving DEFAULT_OG_IMAGE_PATH and the locale-aware helpers unchanged.

---

Outside diff comments:
In `@web/app/`[locale]/(landing)/blog/cmux-history/page.tsx:
- Around line 21-36: Replace the hardcoded English keywords in the blog page
metadata with the localized `t.raw("metaKeywords")` value, following the pattern
used by other blog pages. Add the corresponding `metaKeywords` array to the
`blog.cmuxHistory` namespace in both `en.json` and `ja.json`, ensuring every
supported locale has translations.

In `@web/app/`[locale]/(landing)/blog/gpl/page.tsx:
- Around line 16-19: Replace the hardcoded keywords in the GPL page metadata
with a localized `metaKeywords` value obtained via `next-intl` (for example,
`t.raw("metaKeywords")`), following `blog/cmux-vault/page.tsx`; add the
corresponding `metaKeywords` array to the `blog.gpl` namespace in both
`web/messages/en.json` and `web/messages/ja.json`.

In `@web/tests/security-headers.test.ts`:
- Around line 22-42: The test hardcodes the locale alternation instead of using
the shared routing configuration. Update the test near the “caches only
explicit-locale public marketing pages at the edge” case to derive the localized
route pattern from the locales exported by the shared i18n routing config,
excluding the default locale, and use that generated pattern when matching
`securityHeaderRules`; avoid duplicating locale values.

---

Duplicate comments:
In `@web/app/`[locale]/(landing)/blog/show-hn-launch/page.tsx:
- Around line 19-23: Replace the hardcoded keywords in the page’s metadata with
localized values retrieved via the existing next-intl translation mechanism,
such as t.raw("metaKeywords") before the return. Add the corresponding
metaKeywords array to the blog.showHnLaunch namespaces in both en.json and
ja.json, matching the pattern used by blog/cmux-vault.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e5c0aa12-9c5e-4f68-85ad-4261987d4945

📥 Commits

Reviewing files that changed from the base of the PR and between 94cd1fa and 87b3f4e.

📒 Files selected for processing (71)
  • web/app/[locale]/(landing)/agents/aider/page.tsx
  • web/app/[locale]/(landing)/agents/amp/page.tsx
  • web/app/[locale]/(landing)/agents/claude-code/page.tsx
  • web/app/[locale]/(landing)/agents/codex/page.tsx
  • web/app/[locale]/(landing)/agents/cursor-cli/page.tsx
  • web/app/[locale]/(landing)/agents/gemini-cli/page.tsx
  • web/app/[locale]/(landing)/agents/opencode/page.tsx
  • web/app/[locale]/(landing)/agents/page.tsx
  • web/app/[locale]/(landing)/assets/page.tsx
  • web/app/[locale]/(landing)/best-terminal-for-mac/page.tsx
  • web/app/[locale]/(landing)/blog/claude-code-best-worktree-manager/page.tsx
  • web/app/[locale]/(landing)/blog/cmd-shift-u/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-claude-teams/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-finder/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-history/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-home/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-omo/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-ssh/page.tsx
  • web/app/[locale]/(landing)/blog/cmux-vault/page.tsx
  • web/app/[locale]/(landing)/blog/gpl/page.tsx
  • web/app/[locale]/(landing)/blog/introducing-cmux/page.tsx
  • web/app/[locale]/(landing)/blog/markdown-viewer/page.tsx
  • web/app/[locale]/(landing)/blog/page.tsx
  • web/app/[locale]/(landing)/blog/passkey-auth/page.tsx
  • web/app/[locale]/(landing)/blog/session-restore/page.tsx
  • web/app/[locale]/(landing)/blog/show-hn-launch/page.tsx
  • web/app/[locale]/(landing)/blog/task-manager/page.tsx
  • web/app/[locale]/(landing)/blog/unread-shortcuts/page.tsx
  • web/app/[locale]/(landing)/blog/zen-of-cmux/page.tsx
  • web/app/[locale]/(landing)/built-on-ghostty/page.tsx
  • web/app/[locale]/(landing)/community/page.tsx
  • web/app/[locale]/(landing)/compare/[slug]/page.tsx
  • web/app/[locale]/(landing)/compare/page.tsx
  • web/app/[locale]/(landing)/docs/agent-integrations/claude-code-teams/page.tsx
  • web/app/[locale]/(landing)/docs/agent-integrations/oh-my-claudecode/page.tsx
  • web/app/[locale]/(landing)/docs/agent-integrations/oh-my-codex/page.tsx
  • web/app/[locale]/(landing)/docs/agent-integrations/oh-my-opencode/page.tsx
  • web/app/[locale]/(landing)/docs/api/page.tsx
  • web/app/[locale]/(landing)/docs/browser-automation/page.tsx
  • web/app/[locale]/(landing)/docs/changelog/page.tsx
  • web/app/[locale]/(landing)/docs/concepts/page.tsx
  • web/app/[locale]/(landing)/docs/configuration/page.tsx
  • web/app/[locale]/(landing)/docs/custom-commands/page.tsx
  • web/app/[locale]/(landing)/docs/dock/page.tsx
  • web/app/[locale]/(landing)/docs/getting-started/page.tsx
  • web/app/[locale]/(landing)/docs/ios/page.tsx
  • web/app/[locale]/(landing)/docs/keyboard-shortcuts/page.tsx
  • web/app/[locale]/(landing)/docs/notifications/page.tsx
  • web/app/[locale]/(landing)/docs/remote-tmux/page.tsx
  • web/app/[locale]/(landing)/docs/session-restore/page.tsx
  • web/app/[locale]/(landing)/docs/skills/page.tsx
  • web/app/[locale]/(landing)/docs/ssh/page.tsx
  • web/app/[locale]/(landing)/docs/task-manager/page.tsx
  • web/app/[locale]/(landing)/docs/textbox/page.tsx
  • web/app/[locale]/(landing)/docs/vault/page.tsx
  • web/app/[locale]/(landing)/docs/workspace-groups/page.tsx
  • web/app/[locale]/(landing)/download/confirmation/page.tsx
  • web/app/[locale]/(landing)/guides/page.tsx
  • web/app/[locale]/(landing)/ios/page.tsx
  • web/app/[locale]/(landing)/nightly/page.tsx
  • web/app/[locale]/(landing)/opengraph-image.tsx
  • web/app/[locale]/(landing)/wall-of-love/page.tsx
  • web/app/[locale]/dashboard/subrouter/page.tsx
  • web/app/[locale]/docs/base/page.tsx
  • web/app/[locale]/enterprise/page.tsx
  • web/app/[locale]/layout.tsx
  • web/i18n/seo.ts
  • web/proxy.ts
  • web/security-headers.ts
  • web/tests/security-headers.test.ts
  • web/tests/seo.test.ts
💤 Files with no reviewable changes (1)
  • web/security-headers.ts

Comment thread web/app/[locale]/(landing)/opengraph-image.tsx Outdated
Comment thread web/app/[locale]/(landing)/opengraph-image.tsx Outdated
Comment thread web/i18n/seo.ts Outdated
@lawrencecchen
lawrencecchen merged commit fe87e60 into main Jul 11, 2026
33 checks passed

This branch was successfully deployed

1 active deployment
Preview – cmux — e4403302 Deployed Jul 11, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant