Skip to content

fix(seo): noindex auth and docs crawl#2954

Merged
smakosh merged 5 commits into
mainfrom
fix/seo-audit-p0-p1
Jul 9, 2026
Merged

fix(seo): noindex auth and docs crawl#2954
smakosh merged 5 commits into
mainfrom
fix/seo-audit-p0-p1

Conversation

@smakosh

@smakosh smakosh commented Jul 8, 2026

Copy link
Copy Markdown
Member

Summary

  • Auth/app noindex: login, signup, SSO, onboarding, forgot/reset password, connect, and referral pages emit robots: noindex,nofollow. These paths are intentionally not disallowed in robots.txt — crawlers must be able to fetch a page to see the noindex, otherwise already-indexed URLs stay stuck as "Indexed, though blocked by robots.txt". Only /dashboard and /api remain disallowed (a bare /ref rule would also have prefix-blocked the public /referrals page and referral share previews).
  • Docs crawlability: docs metadataBase + self-canonicals point at docs.llmgateway.io (was marketing homepage) via a shared trailing-slash-safe lib/base-url.ts; add robots.ts + sitemap.ts (no build-time lastModified) and title template %s | LLM Gateway Docs. /docs-og/ stays crawlable (it serves every docs og:image); /llms.mdx markdown mirrors serve X-Robots-Tag: noindex instead of a robots.txt block so AI crawlers can still fetch them.
  • Schema: remove e-commerce shipping/return fields from model Product JSON-LD; fix Organization sameAs GitHub URL to theopenco/llmgateway.
  • Sitemap: drop ~299 thin /models/*/uptime URLs (still linked internally).
  • On-page: stop double brand in titles, trim long meta descriptions, add og:url/canonicals on key hubs, real /blog/category index with H1. Category pages resolve human-readable labels, use slugified canonicals, keep the site og:image, and 404 unknown categories; nano-banana discount variants canonicalize to /nano-banana-simulator/20.

Left force-dynamic in place (env vars loaded at runtime).

Test plan

  • turbo run build --filter=ui --filter=docs passes
  • After deploy: /login and /signup show noindex, nofollow and are NOT blocked by robots.txt
  • After deploy: docs.llmgateway.io/robots.txt and /sitemap.xml 200 with self-canonicals on docs pages
  • After deploy: /llms.mdx/* responses carry X-Robots-Tag: noindex
  • After deploy: model page JSON-LD has no shippingDetails / MerchantReturnPolicy
  • Submit docs sitemap in GSC; request re-index of key docs URLs

Summary by CodeRabbit

  • New Features
    • Enhanced the blog category landing page with dynamic, correctly matched category links and SEO-friendly category routing.
  • Bug Fixes
    • Expanded and standardized SEO metadata (canonical URLs plus richer Open Graph/Twitter previews) across docs and marketing pages.
    • Improved crawl control with updated robots and sitemap behavior, and added noindex headers for raw docs mirrors.
    • Refined structured data/rating offer URLs to reduce duplicated crawl paths.
  • Documentation
    • Updated docs SEO configuration to use a shared base URL consistently.

Block auth/app pages from indexing, fix docs canonicals
with robots/sitemap, drop model shipping schema and
uptime sitemap bloat, and tighten page titles/meta.
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a9579935-6733-42e5-80fb-71d5dc811b4e

📥 Commits

Reviewing files that changed from the base of the PR and between 8a87010 and 1c7f403.

📒 Files selected for processing (2)
  • apps/docs/app/robots.ts
  • apps/docs/app/sitemap.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/docs/app/sitemap.ts

Walkthrough

This PR updates docs and UI metadata routing, crawl directives, blog category pages, and model structured data, while standardizing shared URL/slug helpers across docs and UI.

Changes

Docs and UI SEO updates

Layer / File(s) Summary
Docs metadata and index routes
apps/docs/app/layout.tsx, apps/docs/app/(home)/[[...slug]]/page.tsx, apps/docs/app/robots.ts, apps/docs/app/sitemap.ts, apps/docs/app/llms.mdx/[[...slug]]/route.ts, apps/docs/lib/base-url.ts
Docs metadata now uses a shared base URL, adds canonical/Open Graph fields, and exposes robots/sitemap routes plus noindex headers for raw markdown mirrors.
UI page metadata updates
apps/ui/src/app/{agents,apps,blog,brand,changelog,compare/*,enterprise,features,guides,legal,migration,models,nano-banana-simulator,open-source,pricing,providers,reliability,ship,use-cases,token-cost-calculator}/..., apps/ui/src/app/layout.tsx, apps/ui/src/app/forgot-password/layout.tsx, apps/ui/src/app/login/layout.tsx, apps/ui/src/app/reset-password/layout.tsx, apps/ui/src/app/signup/layout.tsx, apps/ui/src/app/sso/layout.tsx, apps/ui/src/app/ref/[orgId]/page.tsx, apps/ui/src/app/onboarding/layout.tsx
Many UI routes update titles, descriptions, canonical URLs, Open Graph/Twitter fields, robots directives, and root organization schema links.
UI robots, sitemap, and blog categories
apps/ui/src/app/robots.ts, apps/ui/src/app/sitemap.ts, apps/ui/src/app/blog/category/page.tsx, apps/ui/src/app/blog/category/[category]/page.tsx, apps/ui/src/lib/slugify.ts, apps/ui/src/components/blog/list.tsx
UI crawl rules are narrowed, sitemap generation uses the shared slugify helper, and blog category listing/detail pages now derive categories from content and emit metadata.
Model offer schema
apps/ui/src/lib/rating-schema.ts, apps/ui/src/app/models/[name]/page.tsx, apps/ui/src/app/models/[name]/[provider]/page.tsx
The shared digital offer fields export is removed and model offer structured data now uses explicit URLs.

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

Possibly related PRs

  • theopenco/llmgateway#2408: Also modifies apps/ui/src/app/token-cost-calculator/page.tsx around route-level metadata.
  • theopenco/llmgateway#2664: Also changes apps/ui/src/app/models/[name]* and apps/ui/src/lib/rating-schema.ts around model schema generation.

Suggested reviewers: steebchen

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main SEO change around noindexing auth pages and docs crawling behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 fix/seo-audit-p0-p1

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
apps/ui/src/app/reliability/page.tsx (1)

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

Add twitter metadata to avoid incorrect social sharing cards.

Without a twitter block, this page inherits the root layout's generic twitter card (title: "LLM Gateway - Unified API for Multiple LLM Providers"), which is wrong for the reliability page. Comparable pages (enterprise, pricing, legal, migration, providers/[id]) all include page-specific twitter metadata.

🐦 Suggested addition
 	openGraph: {
 		title: "Reliability & 99.9999% Uptime",
 		description:
 			"Automatic failover across providers, real-time health monitoring, and intelligent routing. Never go down, even when your providers do.",
 		url: "https://llmgateway.io/reliability",
 		type: "website",
 	},
+	twitter: {
+		card: "summary_large_image",
+		title: "Reliability & 99.9999% Uptime",
+		description:
+			"Automatic failover across providers, real-time health monitoring, and intelligent routing. Never go down, even when your providers do.",
+	},
 };
🤖 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 `@apps/ui/src/app/reliability/page.tsx` around lines 11 - 23, Add a
page-specific twitter metadata block to the Reliability page’s Metadata export
so it does not inherit the root layout’s generic social card. Update the
`metadata` object in `page.tsx` alongside the existing `title`, `description`,
and `openGraph` fields, matching the pattern used by the enterprise, pricing,
legal, migration, and providers/[id] pages. Include the appropriate `twitter`
values so social sharing for this route uses Reliability-specific text and card
settings.
apps/ui/src/app/ship/page.tsx (1)

8-20: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add twitter metadata to avoid incorrect social sharing cards.

Without a twitter block, this page inherits the root layout's generic twitter card title ("LLM Gateway - Unified API for Multiple LLM Providers"), which is wrong for the ship page. Comparable pages (enterprise, pricing, legal, migration, providers/[id]) all include page-specific twitter metadata.

🐦 Suggested addition
 	openGraph: {
 		title: "Ship an AI App in 10 Minutes",
 		description:
 			"Clone a production-ready AI template, connect to 200+ models, and deploy. Ship your AI app in minutes with LLM Gateway.",
 		url: "https://llmgateway.io/ship",
 		type: "website",
 	},
+	twitter: {
+		card: "summary_large_image",
+		title: "Ship an AI App in 10 Minutes",
+		description:
+			"Clone a production-ready AI template, connect to 200+ models, and deploy. Ship your AI app in minutes with LLM Gateway.",
+	},
 };
🤖 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 `@apps/ui/src/app/ship/page.tsx` around lines 8 - 20, The page metadata in
ship/page.tsx is missing a twitter block, so it inherits the root layout’s
generic social card values. Update the exported metadata object in the ship page
to include page-specific twitter metadata, matching the pattern used in the
enterprise, pricing, legal, migration, and providers/[id] pages, so the ship
page shares the correct title and description.
🧹 Nitpick comments (3)
apps/ui/src/app/blog/category/page.tsx (1)

8-13: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract slugify into a shared utility.

The slugify function is duplicated verbatim in apps/ui/src/app/blog/category/[category]/page.tsx (lines 15-20) and is also referenced in apps/ui/src/app/sitemap.ts. Extracting it to a shared utility (e.g., @/lib/slugify) eliminates the copy-paste and ensures consistent slug generation across all three call sites.

♻️ Proposed refactor

Create a shared utility, e.g. apps/ui/src/lib/slugify.ts:

export function slugify(label: string) {
	return label
		.toLowerCase()
		.replace(/[^a-z0-9]+/g, "-")
		.replace(/(^-|-$)/g, "");
}

Then replace both inline definitions with an import:

-import Link from "next/link";
+import Link from "next/link";
+import { slugify } from "`@/lib/slugify`";
-function slugify(label: string) {
-	return label
-		.toLowerCase()
-		.replace(/[^a-z0-9]+/g, "-")
-		.replace(/(^-|-$)/g, "");
-}
🤖 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 `@apps/ui/src/app/blog/category/page.tsx` around lines 8 - 13, The slugify
logic is duplicated across the blog category page, the category detail page, and
sitemap generation; extract the shared `slugify(label: string)` helper into a
single utility like `@/lib/slugify` and update the existing inline usages in
`page.tsx` and `sitemap.ts` to import it instead of redefining it. Keep the
implementation centralized so all call sites use the same slug generation
behavior.
apps/ui/src/app/legal/[slug]/page.tsx (1)

83-89: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider using openGraph.type: "article" for legal pages.

Migration guide pages use "article" for openGraph.type, but legal pages use "website". Individual content pages (legal documents, guides) are more semantically "article" than "website". This is a minor SEO signal inconsistency.

♻️ Suggested change
 		openGraph: {
 			title: entry.title,
 			description: entry.description ?? "LLM Gateway legal post",
-			type: "website",
+			type: "article",
 			url: `https://llmgateway.io/legal/${entry.slug}`,
 		},
🤖 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 `@apps/ui/src/app/legal/`[slug]/page.tsx around lines 83 - 89, The Open Graph
metadata in the legal page’s metadata generation currently uses openGraph.type
as "website", which is inconsistent with other individual content pages. Update
the metadata in the legal page component to use openGraph.type: "article" in the
same object that sets title, description, and url, so the legal page aligns
semantically with the guide pages.
apps/ui/src/app/enterprise/page.tsx (1)

23-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor: title uses en dash () instead of em dash ().

Most other page titles in this PR use an em dash (), e.g., "LLM Gateway vs Portkey — The Open Portkey Alternative". The enterprise title uses an en dash (): "Enterprise LLM Gateway – SSO, Audit Logs & Guardrails". Consider standardizing for visual consistency across the site.

🤖 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 `@apps/ui/src/app/enterprise/page.tsx` at line 23, The Enterprise page title
string in the metadata uses an en dash instead of the same em dash style used
elsewhere. Update the title in the enterprise page component so it matches the
site’s other page titles, keeping the wording the same but replacing the dash
character for consistency.
🤖 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 `@apps/docs/app/`(home)/[[...slug]]/page.tsx:
- Around line 33-35: Normalize DOCS_URL before building canonical and related
URLs so trailing slashes do not produce double-slash paths. Update the URL
construction in page.tsx around docsBaseUrl/canonicalUrl to use the same
normalization approach as layout.tsx (via new URL(...) or equivalent) before
concatenation, and apply the same fix in robots.ts and sitemap.ts where the docs
base URL is reused. Keep the path joining consistent so canonical, Open Graph,
robots, and sitemap URLs all resolve cleanly.

In `@apps/docs/app/robots.ts`:
- Around line 3-15: Add dynamic rendering to the docs metadata routes that
depend on DOCS_URL so they resolve the runtime host instead of being statically
emitted with the fallback value. Update robots() in robots.ts, and the matching
sitemap route, to export const dynamic = "force-dynamic" alongside the existing
MetadataRoute logic, mirroring the pattern already used in
apps/docs/app/layout.tsx.

---

Outside diff comments:
In `@apps/ui/src/app/reliability/page.tsx`:
- Around line 11-23: Add a page-specific twitter metadata block to the
Reliability page’s Metadata export so it does not inherit the root layout’s
generic social card. Update the `metadata` object in `page.tsx` alongside the
existing `title`, `description`, and `openGraph` fields, matching the pattern
used by the enterprise, pricing, legal, migration, and providers/[id] pages.
Include the appropriate `twitter` values so social sharing for this route uses
Reliability-specific text and card settings.

In `@apps/ui/src/app/ship/page.tsx`:
- Around line 8-20: The page metadata in ship/page.tsx is missing a twitter
block, so it inherits the root layout’s generic social card values. Update the
exported metadata object in the ship page to include page-specific twitter
metadata, matching the pattern used in the enterprise, pricing, legal,
migration, and providers/[id] pages, so the ship page shares the correct title
and description.

---

Nitpick comments:
In `@apps/ui/src/app/blog/category/page.tsx`:
- Around line 8-13: The slugify logic is duplicated across the blog category
page, the category detail page, and sitemap generation; extract the shared
`slugify(label: string)` helper into a single utility like `@/lib/slugify` and
update the existing inline usages in `page.tsx` and `sitemap.ts` to import it
instead of redefining it. Keep the implementation centralized so all call sites
use the same slug generation behavior.

In `@apps/ui/src/app/enterprise/page.tsx`:
- Line 23: The Enterprise page title string in the metadata uses an en dash
instead of the same em dash style used elsewhere. Update the title in the
enterprise page component so it matches the site’s other page titles, keeping
the wording the same but replacing the dash character for consistency.

In `@apps/ui/src/app/legal/`[slug]/page.tsx:
- Around line 83-89: The Open Graph metadata in the legal page’s metadata
generation currently uses openGraph.type as "website", which is inconsistent
with other individual content pages. Update the metadata in the legal page
component to use openGraph.type: "article" in the same object that sets title,
description, and url, so the legal page aligns semantically with the guide
pages.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fd4be167-15d7-404e-841d-2ddf08a86a98

📥 Commits

Reviewing files that changed from the base of the PR and between d9d557c and be5521d.

📒 Files selected for processing (46)
  • apps/docs/app/(home)/[[...slug]]/page.tsx
  • apps/docs/app/layout.tsx
  • apps/docs/app/robots.ts
  • apps/docs/app/sitemap.ts
  • apps/ui/src/app/agents/page.tsx
  • apps/ui/src/app/apps/page.tsx
  • apps/ui/src/app/blog/category/[category]/page.tsx
  • apps/ui/src/app/blog/category/page.tsx
  • apps/ui/src/app/blog/page.tsx
  • apps/ui/src/app/brand/layout.tsx
  • apps/ui/src/app/brand/page.tsx
  • apps/ui/src/app/changelog/[slug]/page.tsx
  • apps/ui/src/app/compare/aws-bedrock/page.tsx
  • apps/ui/src/app/compare/azure-ai-foundry/page.tsx
  • apps/ui/src/app/compare/litellm/page.tsx
  • apps/ui/src/app/compare/open-router/page.tsx
  • apps/ui/src/app/compare/portkey/page.tsx
  • apps/ui/src/app/compare/vercel-ai-gateway/page.tsx
  • apps/ui/src/app/enterprise/page.tsx
  • apps/ui/src/app/features/[slug]/page.tsx
  • apps/ui/src/app/forgot-password/layout.tsx
  • apps/ui/src/app/guides/[slug]/page.tsx
  • apps/ui/src/app/layout.tsx
  • apps/ui/src/app/legal/[slug]/page.tsx
  • apps/ui/src/app/login/layout.tsx
  • apps/ui/src/app/migration/[slug]/page.tsx
  • apps/ui/src/app/models/[name]/[provider]/page.tsx
  • apps/ui/src/app/models/[name]/page.tsx
  • apps/ui/src/app/models/page.tsx
  • apps/ui/src/app/nano-banana-simulator/[discount]/page.tsx
  • apps/ui/src/app/onboarding/layout.tsx
  • apps/ui/src/app/open-source/page.tsx
  • apps/ui/src/app/pricing/page.tsx
  • apps/ui/src/app/providers/[id]/page.tsx
  • apps/ui/src/app/providers/page.tsx
  • apps/ui/src/app/ref/[orgId]/page.tsx
  • apps/ui/src/app/reliability/page.tsx
  • apps/ui/src/app/reset-password/layout.tsx
  • apps/ui/src/app/robots.ts
  • apps/ui/src/app/ship/page.tsx
  • apps/ui/src/app/signup/layout.tsx
  • apps/ui/src/app/sitemap.ts
  • apps/ui/src/app/sso/layout.tsx
  • apps/ui/src/app/token-cost-calculator/page.tsx
  • apps/ui/src/app/use-cases/page.tsx
  • apps/ui/src/lib/rating-schema.ts
💤 Files with no reviewable changes (1)
  • apps/ui/src/lib/rating-schema.ts

Comment thread apps/docs/app/(home)/[[...slug]]/page.tsx Outdated
Comment thread apps/docs/app/robots.ts
Comment on lines +3 to +15
export default function robots(): MetadataRoute.Robots {
const docsBaseUrl = process.env.DOCS_URL ?? "https://docs.llmgateway.io";

return {
rules: [
{
userAgent: "*",
allow: "/",
disallow: ["/api/", "/llms.mdx/", "/docs-og/"],
},
],
sitemap: `${docsBaseUrl}/sitemap.xml`,
};

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if other files in the docs app use force-dynamic alongside env var reads
rg -n "force-dynamic|dynamic.*=.*force" apps/docs/ --type=ts -C2

Repository: theopenco/llmgateway

Length of output: 418


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the docs app files that read DOCS_URL and the robots route itself.
sed -n '1,120p' apps/docs/app/layout.tsx
printf '\n---\n'
sed -n '1,120p' apps/docs/app/robots.ts
printf '\n---\n'
rg -n "DOCS_URL|dynamic = \"force-dynamic\"" apps/docs/app -C 2

Repository: theopenco/llmgateway

Length of output: 3605


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the robots route and nearby app route conventions.
sed -n '1,120p' apps/docs/app/robots.ts
printf '\n---\n'
sed -n '1,80p' apps/docs/app/layout.tsx
printf '\n---\n'
rg -n "export const dynamic|MetadataRoute.Robots|DOCS_URL" apps/docs/app -C 2

Repository: theopenco/llmgateway

Length of output: 3674


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' apps/docs/app/sitemap.ts

Repository: theopenco/llmgateway

Length of output: 685


Add export const dynamic = "force-dynamic" to the docs metadata routes that read DOCS_URL. apps/docs/app/layout.tsx already does this, but apps/docs/app/robots.ts and apps/docs/app/sitemap.ts can otherwise be emitted with the fallback host instead of the runtime value.

🤖 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 `@apps/docs/app/robots.ts` around lines 3 - 15, Add dynamic rendering to the
docs metadata routes that depend on DOCS_URL so they resolve the runtime host
instead of being statically emitted with the fallback value. Update robots() in
robots.ts, and the matching sitemap route, to export const dynamic =
"force-dynamic" alongside the existing MetadataRoute logic, mirroring the
pattern already used in apps/docs/app/layout.tsx.

Comment thread apps/ui/src/app/robots.ts Outdated
Comment thread apps/ui/src/app/robots.ts Outdated
Comment thread apps/docs/app/robots.ts Outdated
Comment thread apps/docs/app/sitemap.ts Outdated
Comment thread apps/docs/app/(home)/[[...slug]]/page.tsx
Comment thread apps/ui/src/app/blog/category/[category]/page.tsx Outdated
Comment thread apps/ui/src/app/blog/category/[category]/page.tsx
Comment thread apps/ui/src/app/blog/category/page.tsx Outdated
Comment thread apps/ui/src/app/blog/category/page.tsx Outdated
Comment thread apps/ui/src/app/nano-banana-simulator/[discount]/page.tsx Outdated
Comment thread apps/ui/src/lib/rating-schema.ts
Address code-review findings on this PR:

- ui robots.txt: stop disallowing noindexed auth/app pages so crawlers
  can see the noindex and actually deindex them; a bare /ref rule also
  prefix-blocked the public /referrals page and referral share previews
- docs robots.txt: unblock /docs-og/ (og:image source) and /llms.mdx
  mirrors; serve X-Robots-Tag: noindex from the llms.mdx route instead
- docs sitemap: drop build-time lastModified (unreliable signal)
- docs: shared trailing-slash-safe docsBaseUrl in lib/base-url.ts
- blog category pages: top-level content-collections imports, shared
  slugify in @/lib/slugify, label-based titles/descriptions, slugified
  canonicals, restored og:image, 404 unknown categories, drop latent
  single-category 307 redirect
- nano-banana: canonicalize all discount variants to /20

@coderabbitai coderabbitai Bot left a comment

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.

Caution

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

⚠️ Outside diff range comments (1)
apps/ui/src/app/nano-banana-simulator/[discount]/page.tsx (1)

34-42: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

og:url should match the canonical URL for non-20 variants.

All discount variants canonicalize to /nano-banana-simulator/20, but og:url remains parameterized (/nano-banana-simulator/${discount}). For any variant other than 20, this creates a mismatch: the canonical says "authoritative version is /20" while og:url says "this page is /{discount}". Social platforms use og:url for engagement aggregation, so fragmented URLs dilute shares across variants instead of consolidating to /20. The blog category page in this same PR correctly aligns og:url with canonical — this page should follow suit.

🔧 Proposed fix: align og:url with canonical
 		openGraph: {
 			title: "Nano Banana Pro Cost Simulator",
 			description: `See how much you save on Gemini 3 Pro image generation. ${discount}% savings vs Google AI Studio pricing.`,
-			url: `https://llmgateway.io/nano-banana-simulator/${discount}`,
+			url: "https://llmgateway.io/nano-banana-simulator/20",
 			type: "website",
 		},
🤖 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 `@apps/ui/src/app/nano-banana-simulator/`[discount]/page.tsx around lines 34 -
42, The Nano Banana simulator metadata is inconsistent because the openGraph URL
in the page component still points to the current discount while the canonical
always points to /nano-banana-simulator/20. Update the metadata in the
[discount]/page.tsx route so the openGraph.url matches the canonical value for
all variants, using the existing page metadata object that defines alternates
and openGraph.
🤖 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.

Outside diff comments:
In `@apps/ui/src/app/nano-banana-simulator/`[discount]/page.tsx:
- Around line 34-42: The Nano Banana simulator metadata is inconsistent because
the openGraph URL in the page component still points to the current discount
while the canonical always points to /nano-banana-simulator/20. Update the
metadata in the [discount]/page.tsx route so the openGraph.url matches the
canonical value for all variants, using the existing page metadata object that
defines alternates and openGraph.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6c7c5197-7322-4c32-89a9-1a2278d8cb76

📥 Commits

Reviewing files that changed from the base of the PR and between be5521d and 703b6f4.

📒 Files selected for processing (13)
  • apps/docs/app/(home)/[[...slug]]/page.tsx
  • apps/docs/app/layout.tsx
  • apps/docs/app/llms.mdx/[[...slug]]/route.ts
  • apps/docs/app/robots.ts
  • apps/docs/app/sitemap.ts
  • apps/docs/lib/base-url.ts
  • apps/ui/src/app/blog/category/[category]/page.tsx
  • apps/ui/src/app/blog/category/page.tsx
  • apps/ui/src/app/nano-banana-simulator/[discount]/page.tsx
  • apps/ui/src/app/robots.ts
  • apps/ui/src/app/sitemap.ts
  • apps/ui/src/components/blog/list.tsx
  • apps/ui/src/lib/slugify.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/docs/lib/base-url.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/docs/app/(home)/[[...slug]]/page.tsx

The root layout's title.template re-appends "| LLM Gateway" to every
<title> (so deduping page titles was correct), but Next.js does not
apply the template to openGraph/twitter titles — dropping the brand
there removed it from social cards entirely. Restore an explicit
brand suffix on og/twitter titles across the affected pages, and add
the missing og:image on /brand and /reliability.
@smakosh

smakosh commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

Audit of the brand-removal in titles (follow-up commit 21e709727):

  • <title> tags were never at risk: the root layout's title.template: "%s | LLM Gateway" re-appends the brand to every page, so deduping page-level titles was correct — before this PR pages like /providers rendered "LLM Providers - LLM Gateway | LLM Gateway".
  • However, Next.js does not apply the title template to openGraph.title / twitter.title, so dropping the brand there removed it from social share cards entirely (~13 pages: brand, changelog, guides, legal, migration, providers, token-cost-calculator, use-cases, reliability, ship, nano-banana, blog categories). Restored an explicit | LLM Gateway suffix on those.
  • Also added the missing og:image on /brand and /reliability (no file-convention image in those segments, and their og blocks shadow the root default).

Verified against the built app: <title>, og:title, og:image, and canonicals all render correctly (including /nano-banana-simulator/35 → canonical /20, og image via the segment's opengraph-image.tsx).

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
apps/ui/src/app/legal/[slug]/page.tsx (1)

83-92: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider using type: "article" for legal pages.

Legal pages are standalone content documents. Using type: "article" instead of type: "website" would be more semantically appropriate for Open Graph and is consistent with how changelog entries are handled in the same PR.

♻️ Suggested change
 		openGraph: {
 			title: `${entry.title} | LLM Gateway`,
 			description: entry.description ?? "LLM Gateway legal post",
-			type: "website",
+			type: "article",
 			url: `https://llmgateway.io/legal/${entry.slug}`,
 		},
🤖 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 `@apps/ui/src/app/legal/`[slug]/page.tsx around lines 83 - 92, Update the
metadata in the legal page’s page component so the Open Graph object for the
slug-based legal entry uses type "article" instead of "website". Make this
change in the metadata generation logic that sets alternates and openGraph,
keeping the existing title, description, and url behavior unchanged.
🤖 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.

Nitpick comments:
In `@apps/ui/src/app/legal/`[slug]/page.tsx:
- Around line 83-92: Update the metadata in the legal page’s page component so
the Open Graph object for the slug-based legal entry uses type "article" instead
of "website". Make this change in the metadata generation logic that sets
alternates and openGraph, keeping the existing title, description, and url
behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 14d08a7e-6749-4052-a115-f249438d0ee1

📥 Commits

Reviewing files that changed from the base of the PR and between 703b6f4 and 21e7097.

📒 Files selected for processing (15)
  • apps/ui/src/app/blog/category/[category]/page.tsx
  • apps/ui/src/app/blog/category/page.tsx
  • apps/ui/src/app/brand/layout.tsx
  • apps/ui/src/app/brand/page.tsx
  • apps/ui/src/app/changelog/[slug]/page.tsx
  • apps/ui/src/app/guides/[slug]/page.tsx
  • apps/ui/src/app/legal/[slug]/page.tsx
  • apps/ui/src/app/migration/[slug]/page.tsx
  • apps/ui/src/app/nano-banana-simulator/[discount]/page.tsx
  • apps/ui/src/app/providers/[id]/page.tsx
  • apps/ui/src/app/providers/page.tsx
  • apps/ui/src/app/reliability/page.tsx
  • apps/ui/src/app/ship/page.tsx
  • apps/ui/src/app/token-cost-calculator/page.tsx
  • apps/ui/src/app/use-cases/page.tsx
✅ Files skipped from review due to trivial changes (3)
  • apps/ui/src/app/reliability/page.tsx
  • apps/ui/src/app/providers/page.tsx
  • apps/ui/src/app/use-cases/page.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/ui/src/app/token-cost-calculator/page.tsx
  • apps/ui/src/app/blog/category/page.tsx
  • apps/ui/src/app/nano-banana-simulator/[discount]/page.tsx
  • apps/ui/src/app/migration/[slug]/page.tsx
  • apps/ui/src/app/brand/layout.tsx
  • apps/ui/src/app/guides/[slug]/page.tsx
  • apps/ui/src/app/brand/page.tsx
  • apps/ui/src/app/blog/category/[category]/page.tsx

A single Offer makes the page a merchant-listing candidate, which
requires shippingDetails/hasMerchantReturnPolicy — fields that do not
apply to a digital API product and were removed earlier in this PR.
AggregateOffer keeps the page eligible for product snippets (price +
rating stars) without merchant-listing field requirements.

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
apps/ui/src/app/models/[name]/[provider]/page.tsx (1)

223-244: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

AggregateOffer structure looks correct.

The switch from Offer to AggregateOffer with offerCount: 1 and matching lowPrice/highPrice is well-formed. Since AggregateOffer extends Offer in schema.org, the inherited priceSpecification, availability, seller, and url properties are all valid here. The explicit url pointing to the provider-specific page is correct — it differs from the canonical (which consolidates to the model page) but that's the right separation: structured data URL reflects the actual page, canonical manages crawl authority.

One optional note: the pricing page precedent (apps/ui/src/app/pricing/page.tsx) includes a nested offers array with individual Offer objects inside its AggregateOffer. This page omits that nested array. Google will likely still parse the snippet correctly given offerCount and lowPrice/highPrice, but adding a nested Offer with the same url and price would make the structured data more complete and consistent with the rest of the codebase.

🤖 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 `@apps/ui/src/app/models/`[name]/[provider]/page.tsx around lines 223 - 244,
The AggregateOffer shape in the model page metadata is valid, but it is missing
the nested individual Offer entry used elsewhere in the app for consistency.
Update the structured data in the page component that builds the schema.org
object so the AggregateOffer includes an offers array with a matching Offer
object (same URL, price, currency, availability, and seller) similar to the
pricing page implementation, while keeping the existing AggregateOffer fields
unchanged.
🤖 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.

Nitpick comments:
In `@apps/ui/src/app/models/`[name]/[provider]/page.tsx:
- Around line 223-244: The AggregateOffer shape in the model page metadata is
valid, but it is missing the nested individual Offer entry used elsewhere in the
app for consistency. Update the structured data in the page component that
builds the schema.org object so the AggregateOffer includes an offers array with
a matching Offer object (same URL, price, currency, availability, and seller)
similar to the pricing page implementation, while keeping the existing
AggregateOffer fields unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7ae3901c-49ff-4424-ac0c-5cbcbf76e0a3

📥 Commits

Reviewing files that changed from the base of the PR and between 21e7097 and 8a87010.

📒 Files selected for processing (1)
  • apps/ui/src/app/models/[name]/[provider]/page.tsx

Metadata routes don't inherit the root layout's force-dynamic, so both
were prerendered at build time with the build-time DOCS_URL (usually
the fallback) baked in. Forcing dynamic rendering resolves the runtime
host, matching the pattern used in apps/docs/app/layout.tsx.
@smakosh smakosh added this pull request to the merge queue Jul 9, 2026
Merged via the queue into main with commit bae7652 Jul 9, 2026
12 checks passed
@smakosh smakosh deleted the fix/seo-audit-p0-p1 branch July 9, 2026 16:08
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