feat(seo): implement Phase 1 technical SEO improvements#117
feat(seo): implement Phase 1 technical SEO improvements#117omsherikar wants to merge 1 commit intomainfrom
Conversation
- Fix homepage meta description length (167 → 153 chars) - Add useSEO hook to CaseStudiesPage, DocsPage, and NotFoundPage - Add dynamic per-page useSEO + BreadcrumbList JSON-LD to CaseStudyDetailPage - Add FAQPage JSON-LD schema to FAQSection for Google rich snippets - Enhance SoftwareApplication schema with offers and featureList - Add seo field to CaseStudy type and populate for existing case study - Clean up sitemap: remove login/signup, fix case study slug, add image metadata - Add CLAUDE.md for repository guidance Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
|
👋 Thanks for opening this pull request! A maintainer will review it soon. Please make sure all CI checks pass. |
📝 WalkthroughWalkthroughThis PR adds comprehensive SEO improvements across the website, including a new project documentation file ( Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Pull request overview
Implements Phase 1 technical SEO improvements across the marketing site and docs by adding per-page meta tag management, expanding structured data, and updating crawl/indexing assets.
Changes:
- Add case-study-specific SEO metadata in the case study data model and apply it on detail pages.
- Add dynamic per-route SEO via
useSEOon Docs, Case Studies, and 404 pages. - Add/adjust structured data and crawl artifacts (FAQ JSON-LD, breadcrumb JSON-LD, sitemap.xml, index.html JSON-LD).
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/data/caseStudies.ts | Adds an optional seo payload to case studies and populates it for the legacy refactoring case study. |
| src/components/NotFoundPage.tsx | Adds useSEO to noindex the 404 page and set basic metadata. |
| src/components/FAQSection.tsx | Injects FAQPage JSON-LD into <head> for rich results. |
| src/components/DocsPage.tsx | Adds useSEO metadata for the docs landing page. |
| src/components/CaseStudyDetailPage.tsx | Adds useSEO using case-study SEO fields and injects BreadcrumbList JSON-LD. |
| src/components/CaseStudiesPage.tsx | Adds useSEO metadata for the case studies index page. |
| public/sitemap.xml | Updates case study URL entry and removes login/signup URLs from sitemap. |
| public/index.html | Updates primary meta description and enriches SoftwareApplication JSON-LD (features + offers). |
| CLAUDE.md | Adds repository guidance/documentation for Claude Code usage and project conventions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ogTitle: 'Documentation | Refactron – Refactoring API & CLI Reference', | ||
| ogDescription: | ||
| 'Explore Refactron documentation: quick start guide, CLI reference, API reference, core concepts, and security practices for AI-assisted code refactoring.', | ||
| canonical: 'https://docs.refactron.dev', |
There was a problem hiding this comment.
useSEO updates OG title/description/image but leaves meta[property="og:url"] as whatever is in public/index.html (currently the homepage). That means link previews for this page may advertise the wrong URL. Consider extending SEOConfig/useSEO to support an ogUrl field (and update meta[property="og:url"]), then pass the page URL here (likely the same value as canonical).
| canonical: 'https://docs.refactron.dev', | |
| canonical: 'https://docs.refactron.dev', | |
| ogUrl: 'https://docs.refactron.dev', |
| ogTitle: | ||
| 'Case Studies | Refactron – Real-World Refactoring Transformations', | ||
| ogDescription: | ||
| 'See how engineering teams use Refactron to safely modernize legacy codebases, reduce technical debt, and improve code maintainability.', |
There was a problem hiding this comment.
This page sets Open Graph title/description but cannot set og:url, so shares may keep the homepage URL from public/index.html. Consider adding ogUrl support to useSEO and setting it here (typically to the canonical URL).
| 'See how engineering teams use Refactron to safely modernize legacy codebases, reduce technical debt, and improve code maintainability.', | |
| 'See how engineering teams use Refactron to safely modernize legacy codebases, reduce technical debt, and improve code maintainability.', | |
| ogUrl: 'https://refactron.dev/case-studies', |
| ogImage: caseStudy?.seo?.ogImage, | ||
| twitterTitle: caseStudy?.seo?.title, | ||
| twitterDescription: caseStudy?.seo?.description, | ||
| twitterImage: caseStudy?.seo?.ogImage, |
There was a problem hiding this comment.
This config sets OG/Twitter titles/descriptions/images but useSEO currently doesn't update meta[property="og:url"]. For per-case-study pages, that will leave og:url pointing at the homepage, which can break link previews/attribution. Consider adding an ogUrl field to useSEO and pass the case-study URL (same as canonical).
| twitterImage: caseStudy?.seo?.ogImage, | |
| twitterImage: caseStudy?.seo?.ogImage, | |
| ogUrl: slug ? `https://refactron.dev/case-studies/${slug}` : undefined, |
| "price": "0", | ||
| "priceCurrency": "USD", |
There was a problem hiding this comment.
In the Enterprise offer, price is set to "0" while the description says "Custom pricing". This makes the structured data internally inconsistent and can lead to incorrect rich results. Consider omitting price/priceCurrency for Enterprise or providing a non-zero/price range that matches the offering.
| "price": "0", | |
| "priceCurrency": "USD", |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
public/index.html (1)
134-140: Enterprise offer pricing is misleading in structured data.The Enterprise offer shows
"price": "0"but the description states "Custom pricing". This creates a semantic mismatch that could confuse search engines and rich result displays. Consider either omitting thepricefield for Enterprise or using a more accurate representation.♻️ Proposed fix
{ "@type": "Offer", "name": "Enterprise", - "price": "0", - "priceCurrency": "USD", "description": "Custom pricing for enterprise teams with on-prem deployment options" }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@public/index.html` around lines 134 - 140, The structured data for the Offer object with "@type": "Offer" and "name": "Enterprise" is misleading because it sets "price": "0" while the description indicates custom pricing; update the JSON-LD by removing the "price" property for the Enterprise offer (or replace it with an appropriate non-numeric representation such as omitting "price" and optionally adding "priceSpecification" or "priceCurrency": null) so the Offer for "Enterprise" accurately reflects custom pricing and avoids conflicting signals to search engines.public/sitemap.xml (1)
9-9: Consider updating homepagelastmoddate.The homepage
lastmodis still2026-01-19, butpublic/index.htmlwas modified in this PR with updated meta description and structured data. Consider updating this to reflect recent changes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@public/sitemap.xml` at line 9, Update the homepage <lastmod> entry in the sitemap by changing the existing <lastmod>2026-01-19</lastmod> value to the actual date when public/index.html was modified (match the commit date of the meta description/structured data change); locate the sitemap homepage entry by the <lastmod> tag for the root URL and update it so the sitemap reflects the recent change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@CLAUDE.md`:
- Around line 53-61: The fenced env-vars block in CLAUDE.md is missing a
language identifier and triggers markdownlint MD040; update the opening fence
for the block that contains REACT_APP_ENABLE_LOCAL_AUTH,
REACT_APP_ENABLE_LOCAL_DOCS, REACT_APP_API_BASE_URL, REACT_APP_GOOGLE_CLIENT_ID,
REACT_APP_GITHUB_CLIENT_ID, REACT_APP_EMAILJS_*, and REACT_APP_SENTRY_DSN from
``` to ```dotenv so the block is annotated as dotenv (keeping the same content
and closing fence).
In `@src/components/CaseStudyDetailPage.tsx`:
- Around line 14-28: The robots meta tag is always set to 'index, follow' even
when caseStudy is undefined; update the useSEO call so robots is conditional:
when caseStudy is falsy set robots to 'noindex, nofollow' (or 'noindex') and
when caseStudy is present keep 'index, follow'; modify the robots property in
the useSEO invocation (alongside existing canonical logic that already uses
slug) so the value depends on caseStudy (refer to useSEO, caseStudy, and slug).
In `@src/data/caseStudies.ts`:
- Around line 147-155: Add a small redirect map in CaseStudyDetailPage.tsx to
remap removed slugs before lookup: define a slugRedirects Record (e.g., mapping
'vectorpay-modernization' and 'orbitai-platform' to
'legacy-code-ai-refactoring'), read the route param via useParams to get slug,
compute resolvedSlug = slug ? (slugRedirects[slug] || slug) : undefined, and
pass resolvedSlug (not the raw slug) into getCaseStudyBySlug to retrieve the
case study so old inbound links are redirected instead of producing a 404.
---
Nitpick comments:
In `@public/index.html`:
- Around line 134-140: The structured data for the Offer object with "@type":
"Offer" and "name": "Enterprise" is misleading because it sets "price": "0"
while the description indicates custom pricing; update the JSON-LD by removing
the "price" property for the Enterprise offer (or replace it with an appropriate
non-numeric representation such as omitting "price" and optionally adding
"priceSpecification" or "priceCurrency": null) so the Offer for "Enterprise"
accurately reflects custom pricing and avoids conflicting signals to search
engines.
In `@public/sitemap.xml`:
- Line 9: Update the homepage <lastmod> entry in the sitemap by changing the
existing <lastmod>2026-01-19</lastmod> value to the actual date when
public/index.html was modified (match the commit date of the meta
description/structured data change); locate the sitemap homepage entry by the
<lastmod> tag for the root URL and update it so the sitemap reflects the recent
change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5c292e33-869e-4444-b713-951264062c75
📒 Files selected for processing (9)
CLAUDE.mdpublic/index.htmlpublic/sitemap.xmlsrc/components/CaseStudiesPage.tsxsrc/components/CaseStudyDetailPage.tsxsrc/components/DocsPage.tsxsrc/components/FAQSection.tsxsrc/components/NotFoundPage.tsxsrc/data/caseStudies.ts
| ``` | ||
| REACT_APP_ENABLE_LOCAL_AUTH=true # Expose auth routes on localhost | ||
| REACT_APP_ENABLE_LOCAL_DOCS=true # Expose docs routes on localhost | ||
| REACT_APP_API_BASE_URL # Backend base URL | ||
| REACT_APP_GOOGLE_CLIENT_ID # OAuth | ||
| REACT_APP_GITHUB_CLIENT_ID # OAuth | ||
| REACT_APP_EMAILJS_* # Contact/newsletter forms | ||
| REACT_APP_SENTRY_DSN # Error tracking | ||
| ``` |
There was a problem hiding this comment.
Add a language identifier to the fenced env-vars block.
markdownlint MD040 is triggered here. Please annotate the fence (e.g., dotenv) to keep docs lint-clean. (Line 53)
Proposed fix
-```
+```dotenv
REACT_APP_ENABLE_LOCAL_AUTH=true # Expose auth routes on localhost
REACT_APP_ENABLE_LOCAL_DOCS=true # Expose docs routes on localhost
REACT_APP_API_BASE_URL # Backend base URL
REACT_APP_GOOGLE_CLIENT_ID # OAuth
REACT_APP_GITHUB_CLIENT_ID # OAuth
REACT_APP_EMAILJS_* # Contact/newsletter forms
REACT_APP_SENTRY_DSN # Error tracking</details>
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.21.0)</summary>
[warning] 53-53: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
Verify each finding against the current code and only fix it if needed.
In @CLAUDE.md around lines 53 - 61, The fenced env-vars block in CLAUDE.md is
missing a language identifier and triggers markdownlint MD040; update the
opening fence for the block that contains REACT_APP_ENABLE_LOCAL_AUTH,
REACT_APP_ENABLE_LOCAL_DOCS, REACT_APP_API_BASE_URL, REACT_APP_GOOGLE_CLIENT_ID,
REACT_APP_GITHUB_CLIENT_ID, REACT_APP_EMAILJS_*, and REACT_APP_SENTRY_DSN from
todotenv so the block is annotated as dotenv (keeping the same content
and closing fence).
</details>
<!-- fingerprinting:phantom:triton:hawk -->
<!-- This is an auto-generated comment by CodeRabbit -->
| useSEO({ | ||
| title: caseStudy?.seo?.title ?? 'Case Study | Refactron', | ||
| description: | ||
| caseStudy?.seo?.description ?? | ||
| 'Real-world refactoring transformations using Refactron — safety-first, behavior-preserving, incremental code improvements.', | ||
| keywords: caseStudy?.seo?.keywords, | ||
| ogTitle: caseStudy?.seo?.title, | ||
| ogDescription: caseStudy?.seo?.description, | ||
| ogImage: caseStudy?.seo?.ogImage, | ||
| twitterTitle: caseStudy?.seo?.title, | ||
| twitterDescription: caseStudy?.seo?.description, | ||
| twitterImage: caseStudy?.seo?.ogImage, | ||
| canonical: slug ? `https://refactron.dev/case-studies/${slug}` : undefined, | ||
| robots: 'index, follow', | ||
| }); |
There was a problem hiding this comment.
Non-existent case studies should use noindex robots directive.
When caseStudy is undefined (invalid slug), the page still sets robots: 'index, follow'. This allows search engines to index error states. Consider setting noindex when the case study doesn't exist:
🛡️ Proposed fix
useSEO({
title: caseStudy?.seo?.title ?? 'Case Study | Refactron',
description:
caseStudy?.seo?.description ??
'Real-world refactoring transformations using Refactron — safety-first, behavior-preserving, incremental code improvements.',
keywords: caseStudy?.seo?.keywords,
ogTitle: caseStudy?.seo?.title,
ogDescription: caseStudy?.seo?.description,
ogImage: caseStudy?.seo?.ogImage,
twitterTitle: caseStudy?.seo?.title,
twitterDescription: caseStudy?.seo?.description,
twitterImage: caseStudy?.seo?.ogImage,
canonical: slug ? `https://refactron.dev/case-studies/${slug}` : undefined,
- robots: 'index, follow',
+ robots: caseStudy ? 'index, follow' : 'noindex, follow',
});📝 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.
| useSEO({ | |
| title: caseStudy?.seo?.title ?? 'Case Study | Refactron', | |
| description: | |
| caseStudy?.seo?.description ?? | |
| 'Real-world refactoring transformations using Refactron — safety-first, behavior-preserving, incremental code improvements.', | |
| keywords: caseStudy?.seo?.keywords, | |
| ogTitle: caseStudy?.seo?.title, | |
| ogDescription: caseStudy?.seo?.description, | |
| ogImage: caseStudy?.seo?.ogImage, | |
| twitterTitle: caseStudy?.seo?.title, | |
| twitterDescription: caseStudy?.seo?.description, | |
| twitterImage: caseStudy?.seo?.ogImage, | |
| canonical: slug ? `https://refactron.dev/case-studies/${slug}` : undefined, | |
| robots: 'index, follow', | |
| }); | |
| useSEO({ | |
| title: caseStudy?.seo?.title ?? 'Case Study | Refactron', | |
| description: | |
| caseStudy?.seo?.description ?? | |
| 'Real-world refactoring transformations using Refactron — safety-first, behavior-preserving, incremental code improvements.', | |
| keywords: caseStudy?.seo?.keywords, | |
| ogTitle: caseStudy?.seo?.title, | |
| ogDescription: caseStudy?.seo?.description, | |
| ogImage: caseStudy?.seo?.ogImage, | |
| twitterTitle: caseStudy?.seo?.title, | |
| twitterDescription: caseStudy?.seo?.description, | |
| twitterImage: caseStudy?.seo?.ogImage, | |
| canonical: slug ? `https://refactron.dev/case-studies/${slug}` : undefined, | |
| robots: caseStudy ? 'index, follow' : 'noindex, follow', | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/CaseStudyDetailPage.tsx` around lines 14 - 28, The robots meta
tag is always set to 'index, follow' even when caseStudy is undefined; update
the useSEO call so robots is conditional: when caseStudy is falsy set robots to
'noindex, nofollow' (or 'noindex') and when caseStudy is present keep 'index,
follow'; modify the robots property in the useSEO invocation (alongside existing
canonical logic that already uses slug) so the value depends on caseStudy (refer
to useSEO, caseStudy, and slug).
| seo: { | ||
| title: | ||
| 'Legacy Code AI Refactoring Case Study | Refactron – 80% Less Effort', | ||
| description: | ||
| 'See how Refactron cut manual refactoring effort by 80%, detected 78 issues per analysis, and doubled developer onboarding speed in a legacy enterprise codebase.', | ||
| keywords: | ||
| 'AI code refactoring case study, legacy code modernization, technical debt reduction, behavior-preserving refactoring, Python refactoring tool', | ||
| ogImage: 'https://refactron.dev/Refactron_Hero.png', | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if there are any redirect mechanisms in place for old slugs
rg -n "vectorpay-modernization|orbitai-platform" --type=ts --type=tsx --type=js
rg -n "redirect" src/Repository: Refactron-ai/Refactron_Website
Length of output: 7199
🏁 Script executed:
# Find and examine CaseStudyDetailPage.tsx
find . -name "*CaseStudyDetailPage*" -o -name "*caseStudy*" -o -name "*CaseStudy*" | grep -E "\.(tsx|ts|jsx|js)$"Repository: Refactron-ai/Refactron_Website
Length of output: 114
🏁 Script executed:
# Examine the caseStudies.ts file to see all current slugs and understand data structure
head -100 src/data/caseStudies.tsRepository: Refactron-ai/Refactron_Website
Length of output: 3235
🏁 Script executed:
# Check routing configuration for case study routes
find . -name "*route*" -o -name "*Route*" -o -name "*router*" | grep -E "\.(tsx|ts|jsx|js)$" | head -20Repository: Refactron-ai/Refactron_Website
Length of output: 109
🏁 Script executed:
# Search for any 404 handling or error messages related to "couldn't find"
rg -i "couldn't find|case.*story|not.*found" src/ --type=ts --type=jsRepository: Refactron-ai/Refactron_Website
Length of output: 658
🏁 Script executed:
# Examine CaseStudyDetailPage.tsx to see how it handles unknown slugs
cat -n src/components/CaseStudyDetailPage.tsxRepository: Refactron-ai/Refactron_Website
Length of output: 15326
🏁 Script executed:
# Check routing configuration - look at main App.tsx for case study routes
rg -n "case.*study|CaseStudy" src/App.tsx -A 3 -B 3Repository: Refactron-ai/Refactron_Website
Length of output: 774
Add redirect mapping for removed case study slugs to preserve SEO and inbound links.
Inbound links to vectorpay-modernization and orbitai-platform now hit the "We couldn't find that story" error page instead of redirecting. Implement slug aliases in CaseStudyDetailPage.tsx before attempting lookup:
Example redirect mapping
const slugRedirects: Record<string, string> = {
'vectorpay-modernization': 'legacy-code-ai-refactoring',
'orbitai-platform': 'legacy-code-ai-refactoring',
};
const { slug } = useParams<{ slug: string }>();
const resolvedSlug = slug ? (slugRedirects[slug] || slug) : undefined;
const caseStudy = resolvedSlug ? getCaseStudyBySlug(resolvedSlug) : undefined;This preserves backlink equity and provides a better user experience than a 404.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/data/caseStudies.ts` around lines 147 - 155, Add a small redirect map in
CaseStudyDetailPage.tsx to remap removed slugs before lookup: define a
slugRedirects Record (e.g., mapping 'vectorpay-modernization' and
'orbitai-platform' to 'legacy-code-ai-refactoring'), read the route param via
useParams to get slug, compute resolvedSlug = slug ? (slugRedirects[slug] ||
slug) : undefined, and pass resolvedSlug (not the raw slug) into
getCaseStudyBySlug to retrieve the case study so old inbound links are
redirected instead of producing a 404.
Summary by CodeRabbit
New Features
Chores